diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java index cc878c016369..7b9b032f8d4c 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java @@ -1141,11 +1141,10 @@ public abstract class GeckoApp // When that's fixed, `this` can change to // `(GeckoApplication) getApplication()` here. GeckoAppShell.setContextGetter(this); - GeckoAppShell.setApplicationContext(getApplicationContext()); GeckoAppShell.setGeckoInterface(this); // We need to set the notification client before launching Gecko, since Gecko could start // sending notifications immediately after startup, which we don't want to lose/crash on. - GeckoAppShell.setNotificationClient(makeNotificationClient()); + GeckoAppShell.setNotificationListener(makeNotificationClient()); // Tell Stumbler to register a local broadcast listener to listen for preference intents. // We do this via intents since we can't easily access Stumbler directly, @@ -2394,7 +2393,8 @@ public abstract class GeckoApp // even if Gecko is running but it was restarted since the notification // was created, the notification won't be handled (bug 849653). if (GeckoThread.isRunning()) { - GeckoAppShell.handleNotification(action, alertName, alertCookie); + ((NotificationClient) GeckoAppShell.getNotificationListener()).onNotificationClick( + alertName); } } diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java index 29c9f04d1ac4..6ea1ededd37b 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java @@ -158,6 +158,7 @@ public class GeckoApplication extends Application mRefWatcher = LeakCanary.install(this); final Context context = getApplicationContext(); + GeckoAppShell.setApplicationContext(context); HardwareUtils.init(context); Clipboard.init(context); FilePicker.init(context); diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 81e5cc6cfbd0..06315c2da610 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -269,6 +269,7 @@ gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x 'gfx/ViewTransform.java', 'InputConnectionListener.java', 'InputMethods.java', + 'NotificationListener.java', 'notifications/AppNotificationClient.java', 'notifications/NotificationClient.java', 'notifications/NotificationHandler.java', diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java index bc40e595ccc9..554c924d02bc 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java @@ -33,7 +33,6 @@ import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.gfx.PanZoomController; -import org.mozilla.gecko.notifications.NotificationClient; import org.mozilla.gecko.permissions.Permissions; import org.mozilla.gecko.util.EventCallback; import org.mozilla.gecko.util.GeckoRequest; @@ -182,13 +181,6 @@ public class GeckoAppShell private static volatile boolean locationHighAccuracyEnabled; - // Accessed by NotificationHelper. This should be encapsulated. - /* package */ static NotificationClient notificationClient; - - public static NotificationClient getNotificationClient() { - return notificationClient; - } - // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB. private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768; @@ -467,7 +459,8 @@ public class GeckoAppShell double altitude, float accuracy, float bearing, float speed, long time); - private static class DefaultListeners implements SensorEventListener, LocationListener { + private static class DefaultListeners + implements SensorEventListener, LocationListener, NotificationListener { @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @@ -577,18 +570,39 @@ public class GeckoAppShell public void onStatusChanged(String provider, int status, Bundle extras) { } + + @Override // NotificationListener + public void showNotification(String name, String cookie, String host, + String title, String text, String imageUrl) { + // Default is to not show the notification, and immediate send close message. + GeckoAppShell.onNotificationClose(name); + } + + @Override // NotificationListener + public void showPersistentNotification(String name, String cookie, String host, + String title, String text, String imageUrl, + String data) { + // Default is to not show the notification, and immediate send close message. + GeckoAppShell.onNotificationClose(name); + } + + @Override // NotificationListener + public void closeNotification(String name) { + // Do nothing. + } } private static final DefaultListeners DEFAULT_LISTENERS = new DefaultListeners(); private static SensorEventListener sSensorListener = DEFAULT_LISTENERS; private static LocationListener sLocationListener = DEFAULT_LISTENERS; + private static NotificationListener sNotificationListener = DEFAULT_LISTENERS; public static SensorEventListener getSensorListener() { return sSensorListener; } public static void setSensorListener(final SensorEventListener listener) { - sSensorListener = listener; + sSensorListener = (listener != null) ? listener : DEFAULT_LISTENERS; } public static LocationListener getLocationListener() { @@ -596,7 +610,15 @@ public class GeckoAppShell } public static void setLocationListener(final LocationListener listener) { - sLocationListener = listener; + sLocationListener = (listener != null) ? listener : DEFAULT_LISTENERS; + } + + public static NotificationListener getNotificationListener() { + return sNotificationListener; + } + + public static void setNotificationListener(final NotificationListener listener) { + sNotificationListener = (listener != null) ? listener : DEFAULT_LISTENERS; } @WrapForJNI(calledFrom = "gecko") @@ -921,97 +943,55 @@ public class GeckoAppShell return geckoInterface.openUriExternal(targetURI, mimeType, packageName, className, action, title); } - /** - * Only called from GeckoApp. - */ - public static void setNotificationClient(NotificationClient client) { - if (notificationClient == null) { - notificationClient = client; - } else { - Log.d(LOGTAG, "Notification client already set"); - } - } - - private static PendingIntent makePersistentNotificationIntent(int notificationID, String type, - String persistentData) { - final Uri.Builder b = new Uri.Builder(); - final Uri u = b.scheme("notification-event") - .path(Integer.toString(notificationID)) - .appendQueryParameter("type", type) - .build(); - final Intent intent = GeckoService.getIntentToCreateServices( - getApplicationContext(), type, persistentData); - intent.setData(u); - - return PendingIntent.getService(getApplicationContext(), 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - @WrapForJNI(dispatchTo = "gecko") private static native void notifyAlertListener(String name, String topic); - @WrapForJNI(calledFrom = "gecko") - private static void showAlertNotification(String imageUrl, String alertTitle, String alertText, - String alertCookie, String alertName, String host, - String persistentData) { - final int notificationID = alertName.hashCode(); - final PendingIntent clickIntent, closeIntent; - - if (persistentData != null) { - clickIntent = makePersistentNotificationIntent( - notificationID, "persistent-notification-click", persistentData); - closeIntent = makePersistentNotificationIntent( - notificationID, "persistent-notification-close", persistentData); - - } else { - notifyAlertListener(alertName, "alertshow"); - - // The intent to launch when the user clicks the expanded notification - final Intent notificationIntent = new Intent(ACTION_ALERT_CALLBACK); - notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, - AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); - notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Put the strings into the intent as an URI - // "alert:?name=&app=&cookie=" - final Uri.Builder b = new Uri.Builder(); - final Uri dataUri = b.scheme("alert") - .path(Integer.toString(notificationID)) - .appendQueryParameter("name", alertName) - .appendQueryParameter("cookie", alertCookie) - .build(); - notificationIntent.setData(dataUri); - - clickIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - closeIntent = null; + /** + * Called by the NotificationListener to notify Gecko that a notification has been + * shown. + */ + public static void onNotificationShow(final String name) { + if (GeckoThread.isRunning()) { + notifyAlertListener(name, "alertshow"); } + } - notificationClient.add(notificationID, imageUrl, host, alertTitle, - alertText, clickIntent, closeIntent); + /** + * Called by the NotificationListener to notify Gecko that a previously shown + * notification has been closed. + */ + public static void onNotificationClose(final String name) { + if (GeckoThread.isRunning()) { + notifyAlertListener(name, "alertfinished"); + } + } + + /** + * Called by the NotificationListener to notify Gecko that a previously shown + * notification has been clicked on. + */ + public static void onNotificationClick(final String name) { + if (GeckoThread.isRunning()) { + notifyAlertListener(name, "alertclickcallback"); + } } @WrapForJNI(calledFrom = "gecko") - private static void closeNotification(String alertName) { - notifyAlertListener(alertName, "alertfinished"); - - final int notificationID = alertName.hashCode(); - notificationClient.remove(notificationID); - } - - public static void handleNotification(String action, String alertName, String alertCookie) { - final int notificationID = alertName.hashCode(); - - if (ACTION_ALERT_CALLBACK.equals(action)) { - notifyAlertListener(alertName, "alertclickcallback"); - - if (notificationClient.isOngoing(notificationID)) { - // When clicked, keep the notification if it displays progress - return; - } + private static void showNotification(String name, String cookie, String title, + String text, String host, String imageUrl, + String persistentData) { + if (persistentData == null) { + getNotificationListener().showNotification(name, cookie, title, text, host, imageUrl); + return; } - closeNotification(alertName); + getNotificationListener().showPersistentNotification( + name, cookie, title, text, host, imageUrl, persistentData); + } + + @WrapForJNI(calledFrom = "gecko") + private static void closeNotification(String name) { + getNotificationListener().closeNotification(name); } @WrapForJNI(calledFrom = "gecko") diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java index 00aec0d01f04..475669d8f6df 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java @@ -87,8 +87,7 @@ public class GeckoService extends Service { @Override // Service public void onCreate() { GeckoAppShell.ensureCrashHandling(); - GeckoAppShell.setApplicationContext(getApplicationContext()); - GeckoAppShell.setNotificationClient(new ServiceNotificationClient(getApplicationContext())); + GeckoAppShell.setNotificationListener(new ServiceNotificationClient(getApplicationContext())); GeckoThread.onResume(); super.onCreate(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java new file mode 100644 index 000000000000..85a68768fa7a --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java @@ -0,0 +1,17 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +public interface NotificationListener +{ + void showNotification(String name, String cookie, String title, String text, + String host, String imageUrl); + + void showPersistentNotification(String name, String cookie, String title, String text, + String host, String imageUrl, String data); + + void closeNotification(String name); +} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java index cb1ed749aa49..fb2179ea348a 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java @@ -303,7 +303,7 @@ public final class NotificationHelper implements GeckoEventListener { PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT); builder.setDeleteIntent(deletePendingIntent); - GeckoAppShell.getNotificationClient().add(id.hashCode(), builder.build()); + ((NotificationClient) GeckoAppShell.getNotificationListener()).add(id.hashCode(), builder.build()); boolean persistent = message.optBoolean(PERSISTENT_ATTR); // We add only not persistent notifications to the list since we want to purge only @@ -344,7 +344,7 @@ public final class NotificationHelper implements GeckoEventListener { } private void closeNotification(String id, String handlerKey, String cookie) { - GeckoAppShell.getNotificationClient().remove(id.hashCode()); + ((NotificationClient) GeckoAppShell.getNotificationListener()).remove(id.hashCode()); sendNotificationWasClosed(id, handlerKey, cookie); } diff --git a/widget/android/AndroidAlerts.cpp b/widget/android/AndroidAlerts.cpp index 4b919a8af433..7b6ff3ce13e2 100644 --- a/widget/android/AndroidAlerts.cpp +++ b/widget/android/AndroidAlerts.cpp @@ -84,8 +84,8 @@ AndroidAlerts::ShowPersistentNotification(const nsAString& aPersistentData, sAlertInfoMap->Put(name, new AlertInfo{aAlertListener, cookie}); } - java::GeckoAppShell::ShowAlertNotification( - imageUrl, title, text, cookie, name, host, + java::GeckoAppShell::ShowNotification( + name, cookie, title, text, host, imageUrl, !aPersistentData.IsEmpty() ? jni::StringParam(aPersistentData) : jni::StringParam(nullptr)); return NS_OK;