mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 823285 - Use foreground notification service for downloads. r=kats
--HG-- extra : rebase_source : 004e31f0379819c5f72420223b24ed0ceccb2e2e
This commit is contained in:
parent
c4d3dc87a3
commit
7243a415b3
@ -28,26 +28,50 @@ public class AlertNotification
|
||||
private final String mText;
|
||||
private final NotificationManager mNotificationManager;
|
||||
|
||||
private boolean mProgressStyle; // = false
|
||||
private boolean mProgressStyle;
|
||||
private double mPrevPercent = -1;
|
||||
private String mPrevAlertText = "";
|
||||
|
||||
private static final double UPDATE_THRESHOLD = .01;
|
||||
private Context mContext;
|
||||
|
||||
public AlertNotification(Context aContext, int aNotificationId, int aIcon,
|
||||
String aTitle, String aText, long aWhen) {
|
||||
String aTitle, String aText, long aWhen, Uri aIconUri) {
|
||||
super(aIcon, (aText.length() > 0) ? aText : aTitle, aWhen);
|
||||
|
||||
mIcon = aIcon;
|
||||
mTitle = aTitle;
|
||||
mText = aText;
|
||||
mId = aNotificationId;
|
||||
mContext = aContext;
|
||||
|
||||
mNotificationManager = (NotificationManager)
|
||||
aContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager = (NotificationManager) aContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
if (aIconUri == null || aIconUri.getScheme() == null)
|
||||
return;
|
||||
|
||||
// Custom view
|
||||
int layout = R.layout.notification_icon_text;
|
||||
RemoteViews view = new RemoteViews(mContext.getPackageName(), layout);
|
||||
try {
|
||||
URL url = new URL(aIconUri.toString());
|
||||
Bitmap bm = BitmapFactory.decodeStream(url.openStream());
|
||||
view.setImageViewBitmap(R.id.notification_image, bm);
|
||||
view.setTextViewText(R.id.notification_title, mTitle);
|
||||
if (mText.length() > 0) {
|
||||
view.setTextViewText(R.id.notification_text, mText);
|
||||
}
|
||||
contentView = view;
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "failed to create bitmap", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProgressStyle() {
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public synchronized boolean isProgressStyle() {
|
||||
return mProgressStyle;
|
||||
}
|
||||
|
||||
@ -59,34 +83,12 @@ public class AlertNotification
|
||||
mNotificationManager.cancel(mId);
|
||||
}
|
||||
|
||||
public void setCustomIcon(Uri aIconUri) {
|
||||
if (aIconUri == null || aIconUri.getScheme() == null)
|
||||
return;
|
||||
|
||||
// Custom view
|
||||
int layout = R.layout.notification_icon_text;
|
||||
RemoteViews view = new RemoteViews(GeckoApp.mAppContext.getPackageName(), layout);
|
||||
try {
|
||||
URL url = new URL(aIconUri.toString());
|
||||
Bitmap bm = BitmapFactory.decodeStream(url.openStream());
|
||||
view.setImageViewBitmap(R.id.notification_image, bm);
|
||||
view.setTextViewText(R.id.notification_title, mTitle);
|
||||
if (mText.length() > 0) {
|
||||
view.setTextViewText(R.id.notification_text, mText);
|
||||
}
|
||||
contentView = view;
|
||||
mNotificationManager.notify(mId, this);
|
||||
} catch(Exception ex) {
|
||||
Log.e(LOGTAG, "failed to create bitmap", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(String aAlertText, long aProgress, long aProgressMax) {
|
||||
public synchronized void updateProgress(String aAlertText, long aProgress, long aProgressMax) {
|
||||
if (!mProgressStyle) {
|
||||
// Custom view
|
||||
int layout = aAlertText.length() > 0 ? R.layout.notification_progress_text : R.layout.notification_progress;
|
||||
|
||||
RemoteViews view = new RemoteViews(GeckoApp.mAppContext.getPackageName(), layout);
|
||||
RemoteViews view = new RemoteViews(mContext.getPackageName(), layout);
|
||||
view.setImageViewResource(R.id.notification_image, mIcon);
|
||||
view.setTextViewText(R.id.notification_title, mTitle);
|
||||
contentView = view;
|
||||
|
@ -273,6 +273,11 @@
|
||||
android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:exported="false"
|
||||
android:name="org.mozilla.gecko.NotificationService">
|
||||
</service>
|
||||
|
||||
|
||||
#include ../services/manifests/AnnouncementsAndroidManifest_services.xml.in
|
||||
#include ../services/manifests/SyncAndroidManifest_services.xml.in
|
||||
|
@ -1094,7 +1094,7 @@ abstract public class GeckoApp
|
||||
final String fileName = aSrc.substring(aSrc.lastIndexOf("/") + 1);
|
||||
final PendingIntent emptyIntent = PendingIntent.getActivity(mAppContext, 0, new Intent(), 0);
|
||||
final AlertNotification notification = new AlertNotification(mAppContext, fileName.hashCode(),
|
||||
R.drawable.alert_download, fileName, progText, System.currentTimeMillis() );
|
||||
R.drawable.alert_download, fileName, progText, System.currentTimeMillis(), null);
|
||||
notification.setLatestEventInfo(mAppContext, fileName, progText, emptyIntent );
|
||||
notification.flags |= Notification.FLAG_ONGOING_EVENT;
|
||||
notification.show();
|
||||
@ -1677,8 +1677,6 @@ abstract public class GeckoApp
|
||||
}
|
||||
});
|
||||
|
||||
final GeckoApp self = this;
|
||||
|
||||
// End of the startup of our Java App
|
||||
mJavaUiStartupTimer.stop();
|
||||
|
||||
@ -1731,8 +1729,9 @@ abstract public class GeckoApp
|
||||
} else {
|
||||
onSearchRequested();
|
||||
}
|
||||
} else if (ACTION_ALERT_CALLBACK.equals(action)) {
|
||||
processAlertCallback(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GeckoProfile getProfile() {
|
||||
@ -1820,6 +1819,21 @@ abstract public class GeckoApp
|
||||
}
|
||||
}
|
||||
|
||||
private void processAlertCallback(Intent intent) {
|
||||
String alertName = "";
|
||||
String alertCookie = "";
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
alertName = data.getQueryParameter("name");
|
||||
if (alertName == null)
|
||||
alertName = "";
|
||||
alertCookie = data.getQueryParameter("cookie");
|
||||
if (alertCookie == null)
|
||||
alertCookie = "";
|
||||
}
|
||||
handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
|
||||
@ -1860,18 +1874,7 @@ abstract public class GeckoApp
|
||||
String uri = getURIFromIntent(intent);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
|
||||
} else if (ACTION_ALERT_CALLBACK.equals(action)) {
|
||||
String alertName = "";
|
||||
String alertCookie = "";
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
alertName = data.getQueryParameter("name");
|
||||
if (alertName == null)
|
||||
alertName = "";
|
||||
alertCookie = data.getQueryParameter("cookie");
|
||||
if (alertCookie == null)
|
||||
alertCookie = "";
|
||||
}
|
||||
handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
|
||||
processAlertCallback(intent);
|
||||
} else if (ACTION_WIDGET.equals(action)) {
|
||||
addTab();
|
||||
}
|
||||
@ -2012,11 +2015,6 @@ abstract public class GeckoApp
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
// Tell Gecko to shutting down; we'll end up calling System.exit()
|
||||
// in onXreExit.
|
||||
if (isFinishing())
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createShutdownEvent());
|
||||
|
||||
unregisterEventListener("log");
|
||||
unregisterEventListener("Reader:Added");
|
||||
unregisterEventListener("Reader:Removed");
|
||||
@ -2173,7 +2171,12 @@ abstract public class GeckoApp
|
||||
}
|
||||
|
||||
public void handleNotification(String action, String alertName, String alertCookie) {
|
||||
GeckoAppShell.handleNotification(action, alertName, alertCookie);
|
||||
// If Gecko isn't running yet, we ignore the notification. Note that
|
||||
// even if Gecko is running but it was restarted since the notification
|
||||
// was created, the notification won't be handled (bug 849653).
|
||||
if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
|
||||
GeckoAppShell.handleNotification(action, alertName, alertCookie);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMigrateProfile() {
|
||||
|
@ -80,10 +80,8 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -111,9 +109,6 @@ public class GeckoAppShell
|
||||
|
||||
static private GeckoEditableListener mEditableListener = null;
|
||||
|
||||
static private final HashMap<Integer, AlertNotification>
|
||||
mAlertNotifications = new HashMap<Integer, AlertNotification>();
|
||||
|
||||
/* Keep in sync with constants found here:
|
||||
http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
|
||||
*/
|
||||
@ -157,6 +152,7 @@ public class GeckoAppShell
|
||||
private static Handler sGeckoHandler;
|
||||
|
||||
static ActivityHandlerHelper sActivityHelper = new ActivityHandlerHelper();
|
||||
static NotificationServiceClient sNotificationClient;
|
||||
|
||||
/* The Android-side API: API methods that Android calls */
|
||||
|
||||
@ -1185,6 +1181,14 @@ public class GeckoAppShell
|
||||
}});
|
||||
}
|
||||
|
||||
public static void setNotificationClient(NotificationServiceClient client) {
|
||||
if (sNotificationClient == null) {
|
||||
sNotificationClient = client;
|
||||
} else {
|
||||
Log.w(LOGTAG, "Notification client already set");
|
||||
}
|
||||
}
|
||||
|
||||
public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
|
||||
String aAlertCookie, String aAlertName) {
|
||||
Log.d(LOGTAG, "GeckoAppShell.showAlertNotification\n" +
|
||||
@ -1194,72 +1198,41 @@ public class GeckoAppShell
|
||||
"- cookie = '" + aAlertCookie +"'\n" +
|
||||
"- name = '" + aAlertName + "'");
|
||||
|
||||
int icon = R.drawable.ic_status_logo;
|
||||
|
||||
Uri imageUri = Uri.parse(aImageUrl);
|
||||
String scheme = imageUri.getScheme();
|
||||
if ("drawable".equals(scheme)) {
|
||||
String resource = imageUri.getSchemeSpecificPart();
|
||||
resource = resource.substring(resource.lastIndexOf('/') + 1);
|
||||
try {
|
||||
Class<R.drawable> drawableClass = R.drawable.class;
|
||||
Field f = drawableClass.getField(resource);
|
||||
icon = f.getInt(null);
|
||||
} catch (Exception e) {} // just means the resource doesn't exist
|
||||
imageUri = null;
|
||||
}
|
||||
|
||||
int notificationID = aAlertName.hashCode();
|
||||
|
||||
// Remove the old notification with the same ID, if any
|
||||
removeNotification(notificationID);
|
||||
|
||||
AlertNotification notification =
|
||||
new AlertNotification(GeckoApp.mAppContext,notificationID, icon,
|
||||
aAlertTitle, aAlertText,
|
||||
System.currentTimeMillis());
|
||||
|
||||
// The intent to launch when the user clicks the expanded notification
|
||||
Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLICK);
|
||||
notificationIntent.setClassName(GeckoApp.mAppContext,
|
||||
GeckoApp.mAppContext.getPackageName() + ".NotificationHandler");
|
||||
|
||||
int notificationID = aAlertName.hashCode();
|
||||
|
||||
// Put the strings into the intent as an URI "alert:?name=<alertName>&app=<appName>&cookie=<cookie>"
|
||||
Uri.Builder b = new Uri.Builder();
|
||||
String app = GeckoApp.mAppContext.getClass().getName();
|
||||
Uri dataUri = b.scheme("alert")
|
||||
.path(Integer.toString(notificationID))
|
||||
.appendQueryParameter("name", aAlertName)
|
||||
.appendQueryParameter("app", app)
|
||||
.appendQueryParameter("cookie", aAlertCookie)
|
||||
.build();
|
||||
Uri dataUri = b.scheme("alert").path(Integer.toString(notificationID))
|
||||
.appendQueryParameter("name", aAlertName)
|
||||
.appendQueryParameter("app", app)
|
||||
.appendQueryParameter("cookie", aAlertCookie)
|
||||
.build();
|
||||
notificationIntent.setData(dataUri);
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, notificationIntent, 0);
|
||||
notification.setLatestEventInfo(GeckoApp.mAppContext, aAlertTitle, aAlertText, contentIntent);
|
||||
notification.setCustomIcon(imageUri);
|
||||
|
||||
// The intent to execute when the status entry is deleted by the user with the "Clear All Notifications" button
|
||||
Intent clearNotificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLEAR);
|
||||
clearNotificationIntent.setClassName(GeckoApp.mAppContext,
|
||||
GeckoApp.mAppContext.getPackageName() + ".NotificationHandler");
|
||||
clearNotificationIntent.setData(dataUri);
|
||||
notification.deleteIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, clearNotificationIntent, 0);
|
||||
PendingIntent clearIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, clearNotificationIntent, 0);
|
||||
|
||||
mAlertNotifications.put(notificationID, notification);
|
||||
|
||||
notification.show();
|
||||
sNotificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent, clearIntent);
|
||||
}
|
||||
|
||||
public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
|
||||
final int notificationID = aAlertName.hashCode();
|
||||
AlertNotification notification = mAlertNotifications.get(notificationID);
|
||||
if (notification != null)
|
||||
notification.updateProgress(aAlertText, aProgress, aProgressMax);
|
||||
int notificationID = aAlertName.hashCode();
|
||||
sNotificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
|
||||
|
||||
if (aProgress == aProgressMax) {
|
||||
// Hide the notification at 100%
|
||||
removeObserver(aAlertName);
|
||||
removeNotification(notificationID);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1276,9 +1249,8 @@ public class GeckoAppShell
|
||||
if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) {
|
||||
callObserver(aAlertName, "alertclickcallback", aAlertCookie);
|
||||
|
||||
AlertNotification notification = mAlertNotifications.get(notificationID);
|
||||
if (notification != null && notification.isProgressStyle()) {
|
||||
// When clicked, keep the notification, if it displays a progress
|
||||
if (sNotificationClient.isProgressStyle(notificationID)) {
|
||||
// When clicked, keep the notification if it displays progress
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1295,11 +1267,7 @@ public class GeckoAppShell
|
||||
}
|
||||
|
||||
private static void removeNotification(int notificationID) {
|
||||
mAlertNotifications.remove(notificationID);
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager)
|
||||
GeckoApp.mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(notificationID);
|
||||
sNotificationClient.remove(notificationID);
|
||||
}
|
||||
|
||||
public static int getDpi() {
|
||||
|
@ -31,6 +31,8 @@ public class GeckoApplication extends Application {
|
||||
GeckoBatteryManager.getInstance().start();
|
||||
GeckoNetworkManager.getInstance().init(getApplicationContext());
|
||||
MemoryMonitor.getInstance().init(getApplicationContext());
|
||||
GeckoAppShell.setNotificationClient(new NotificationServiceClient(getApplicationContext()));
|
||||
|
||||
mInited = true;
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,8 @@ FENNEC_JAVA_FILES = \
|
||||
MenuPanel.java \
|
||||
MenuPopup.java \
|
||||
MultiChoicePreference.java \
|
||||
NotificationService.java \
|
||||
NotificationServiceClient.java \
|
||||
NSSBridge.java \
|
||||
CustomEditText.java \
|
||||
OnInterceptTouchListener.java \
|
||||
|
175
mobile/android/base/NotificationService.java
Normal file
175
mobile/android/base/NotificationService.java
Normal file
@ -0,0 +1,175 @@
|
||||
/* -*- 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;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class NotificationService extends Service {
|
||||
private final IBinder mBinder = new NotificationBinder();
|
||||
|
||||
private final ConcurrentHashMap<Integer, AlertNotification>
|
||||
mAlertNotifications = new ConcurrentHashMap<Integer, AlertNotification>();
|
||||
|
||||
/**
|
||||
* Notification associated with this service's foreground state.
|
||||
*
|
||||
* {@link android.app.Service#startForeground(int, android.app.Notification)}
|
||||
* associates the foreground with exactly one notification from the service.
|
||||
* To keep Fennec alive during downloads (and to make sure it can be killed
|
||||
* once downloads are complete), we make sure that the foreground is always
|
||||
* associated with an active progress notification if and only if at least
|
||||
* one download is in progress.
|
||||
*/
|
||||
private AlertNotification mForegroundNotification;
|
||||
|
||||
public class NotificationBinder extends Binder {
|
||||
NotificationService getService() {
|
||||
// Return this instance of NotificationService so clients can call public methods
|
||||
return NotificationService.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification.
|
||||
*
|
||||
* @param notificationID the unique ID of the notification
|
||||
* @param aImageUrl URL of the image to use
|
||||
* @param aAlertTitle title of the notification
|
||||
* @param aAlertText text of the notification
|
||||
* @param contentIntent Intent used when the notification is clicked
|
||||
* @param clearIntent Intent used when the notification is removed
|
||||
*/
|
||||
public void add(int notificationID, String aImageUrl, String aAlertTitle,
|
||||
String aAlertText, PendingIntent contentIntent, PendingIntent clearIntent) {
|
||||
// Remove the old notification with the same ID, if any
|
||||
remove(notificationID);
|
||||
|
||||
int icon = R.drawable.ic_status_logo;
|
||||
|
||||
Uri imageUri = Uri.parse(aImageUrl);
|
||||
final String scheme = imageUri.getScheme();
|
||||
if ("drawable".equals(scheme)) {
|
||||
String resource = imageUri.getSchemeSpecificPart();
|
||||
resource = resource.substring(resource.lastIndexOf('/') + 1);
|
||||
try {
|
||||
final Class<R.drawable> drawableClass = R.drawable.class;
|
||||
final Field f = drawableClass.getField(resource);
|
||||
icon = f.getInt(null);
|
||||
} catch (final Exception e) {} // just means the resource doesn't exist
|
||||
imageUri = null;
|
||||
}
|
||||
|
||||
final AlertNotification notification = new AlertNotification(this, notificationID,
|
||||
icon, aAlertTitle, aAlertText, System.currentTimeMillis(), imageUri);
|
||||
|
||||
notification.setLatestEventInfo(this, aAlertTitle, aAlertText, contentIntent);
|
||||
notification.deleteIntent = clearIntent;
|
||||
|
||||
notification.show();
|
||||
mAlertNotifications.put(notification.getId(), notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a notification.
|
||||
*
|
||||
* @param notificationID ID of existing notification
|
||||
* @param aProgress progress of item being updated
|
||||
* @param aProgressMax max progress of item being updated
|
||||
* @param aAlertText text of the notification
|
||||
*/
|
||||
public void update(int notificationID, long aProgress, long aProgressMax, String aAlertText) {
|
||||
final AlertNotification notification = mAlertNotifications.get(notificationID);
|
||||
if (notification == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
notification.updateProgress(aAlertText, aProgress, aProgressMax);
|
||||
|
||||
if (mForegroundNotification == null && notification.isProgressStyle()) {
|
||||
setForegroundNotification(notification);
|
||||
}
|
||||
|
||||
// Hide the notification at 100%
|
||||
if (aProgress == aProgressMax) {
|
||||
remove(notificationID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a notification.
|
||||
*
|
||||
* @param notificationID ID of existing notification
|
||||
*/
|
||||
public void remove(int notificationID) {
|
||||
final AlertNotification notification = mAlertNotifications.remove(notificationID);
|
||||
if (notification != null) {
|
||||
updateForegroundNotification(notification);
|
||||
notification.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the service is done.
|
||||
*
|
||||
* The service is considered finished when all notifications have been
|
||||
* removed.
|
||||
*
|
||||
* @return whether all notifications have been removed
|
||||
*/
|
||||
public boolean isDone() {
|
||||
return mAlertNotifications.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a notification is showing progress.
|
||||
*
|
||||
* @param notificationID the notification to check
|
||||
* @return whether the notification is progress style
|
||||
*/
|
||||
public boolean isProgressStyle(int notificationID) {
|
||||
final AlertNotification notification = mAlertNotifications.get(notificationID);
|
||||
return notification != null && notification.isProgressStyle();
|
||||
}
|
||||
|
||||
private void setForegroundNotification(AlertNotification notification) {
|
||||
mForegroundNotification = notification;
|
||||
if (notification == null) {
|
||||
stopForeground(true);
|
||||
} else {
|
||||
startForeground(notification.getId(), notification);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateForegroundNotification(AlertNotification oldNotification) {
|
||||
if (mForegroundNotification == oldNotification) {
|
||||
// If we're removing the notification associated with the
|
||||
// foreground, we need to pick another active notification to act
|
||||
// as the foreground notification.
|
||||
AlertNotification foregroundNotification = null;
|
||||
for (final AlertNotification notification : mAlertNotifications.values()) {
|
||||
if (notification.isProgressStyle()) {
|
||||
foregroundNotification = notification;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setForegroundNotification(foregroundNotification);
|
||||
}
|
||||
}
|
||||
}
|
227
mobile/android/base/NotificationServiceClient.java
Normal file
227
mobile/android/base/NotificationServiceClient.java
Normal file
@ -0,0 +1,227 @@
|
||||
/* -*- 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;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Client for posting notifications through the NotificationService.
|
||||
*/
|
||||
public class NotificationServiceClient {
|
||||
private static final String LOGTAG = "GeckoNotificationServiceClient";
|
||||
|
||||
private volatile NotificationService mService;
|
||||
private final ServiceConnection mConnection = new NotificationServiceConnection();
|
||||
private boolean mBound;
|
||||
private final Context mContext;
|
||||
private final LinkedList<Runnable> mTaskQueue = new LinkedList<Runnable>();
|
||||
private final ConcurrentHashMap<Integer, UpdateRunnable> mUpdatesMap =
|
||||
new ConcurrentHashMap<Integer, UpdateRunnable>();
|
||||
|
||||
public NotificationServiceClient(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable that is reused between update notifications.
|
||||
*
|
||||
* Updates happen frequently, so reusing Runnables prevents frequent dynamic allocation.
|
||||
*/
|
||||
private class UpdateRunnable implements Runnable {
|
||||
private long mProgress;
|
||||
private long mProgressMax;
|
||||
private String mAlertText;
|
||||
final private int mNotificationID;
|
||||
|
||||
public UpdateRunnable(int notificationID) {
|
||||
mNotificationID = notificationID;
|
||||
}
|
||||
|
||||
public synchronized boolean updateProgress(long progress, long progressMax, String alertText) {
|
||||
if (progress == mProgress
|
||||
&& mProgressMax == progressMax
|
||||
&& TextUtils.equals(mAlertText, alertText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mProgress = progress;
|
||||
mProgressMax = progressMax;
|
||||
mAlertText = alertText;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long progress;
|
||||
long progressMax;
|
||||
String alertText;
|
||||
|
||||
synchronized (this) {
|
||||
progress = mProgress;
|
||||
progressMax = mProgressMax;
|
||||
alertText = mAlertText;
|
||||
}
|
||||
|
||||
mService.update(mNotificationID, progress, progressMax, alertText);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a notification.
|
||||
*
|
||||
* @see NotificationService#add(int, String, String, String, PendingIntent, PendingIntent)
|
||||
*/
|
||||
public synchronized void add(final int notificationID, final String aImageUrl,
|
||||
final String aAlertTitle, final String aAlertText, final PendingIntent contentIntent,
|
||||
final PendingIntent clearIntent) {
|
||||
mTaskQueue.add(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mService.add(notificationID, aImageUrl, aAlertTitle, aAlertText,
|
||||
contentIntent, clearIntent);
|
||||
}
|
||||
});
|
||||
notify();
|
||||
|
||||
if (!mBound) {
|
||||
bind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a notification.
|
||||
*
|
||||
* @see NotificationService#update(int, long, long, String)
|
||||
*/
|
||||
public void update(final int notificationID, final long aProgress, final long aProgressMax,
|
||||
final String aAlertText) {
|
||||
UpdateRunnable runnable = mUpdatesMap.get(notificationID);
|
||||
|
||||
if (runnable == null) {
|
||||
runnable = new UpdateRunnable(notificationID);
|
||||
mUpdatesMap.put(notificationID, runnable);
|
||||
}
|
||||
|
||||
// If we've already posted an update with these values, there's no
|
||||
// need to do it again.
|
||||
if (!runnable.updateProgress(aProgress, aProgressMax, aAlertText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (mBound) {
|
||||
mTaskQueue.add(runnable);
|
||||
notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a notification.
|
||||
*
|
||||
* @see NotificationService#remove(int)
|
||||
*/
|
||||
public synchronized void remove(final int notificationID) {
|
||||
if (!mBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTaskQueue.add(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mService.remove(notificationID);
|
||||
mUpdatesMap.remove(notificationID);
|
||||
}
|
||||
});
|
||||
notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a notification is showing progress.
|
||||
*
|
||||
* @see NotificationService#isProgressStyle(int)
|
||||
*/
|
||||
public boolean isProgressStyle(int notificationID) {
|
||||
final NotificationService service = mService;
|
||||
return service != null && service.isProgressStyle(notificationID);
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
mBound = true;
|
||||
final Intent intent = new Intent(mContext, NotificationService.class);
|
||||
mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
private void unbind() {
|
||||
if (mBound) {
|
||||
mBound = false;
|
||||
mContext.unbindService(mConnection);
|
||||
mUpdatesMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationServiceConnection implements ServiceConnection, Runnable {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
final NotificationService.NotificationBinder binder =
|
||||
(NotificationService.NotificationBinder) service;
|
||||
mService = binder.getService();
|
||||
|
||||
new Thread(this).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
// This is called when the connection with the service has been
|
||||
// unexpectedly disconnected -- that is, its process crashed.
|
||||
// Because it is running in our same process, we should never
|
||||
// see this happen, and the correctness of this class relies on
|
||||
// this never happening.
|
||||
Log.e(LOGTAG, "Notification service disconnected", new Exception());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Runnable r;
|
||||
try {
|
||||
while (true) {
|
||||
// Synchronize polls to prevent tasks from being added to the queue
|
||||
// during the isDone check.
|
||||
synchronized (NotificationServiceClient.this) {
|
||||
r = mTaskQueue.poll();
|
||||
while (r == null) {
|
||||
if (mService.isDone()) {
|
||||
// If there are no more tasks and no notifications being
|
||||
// displayed, the service is disconnected. Unfortunately,
|
||||
// since completed download notifications are shown by
|
||||
// removing the progress notification and creating a new
|
||||
// static one, this will cause the service to be unbound
|
||||
// and immediately rebound when a download completes.
|
||||
unbind();
|
||||
return;
|
||||
}
|
||||
NotificationServiceClient.this.wait();
|
||||
r = mTaskQueue.poll();
|
||||
}
|
||||
}
|
||||
r.run();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(LOGTAG, "Notification task queue processing interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user