diff options
author | Dianne Hackborn <hackbod@google.com> | 2009-08-17 23:33:56 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2009-08-18 13:59:27 -0700 |
commit | d8a43f61680bacf0d4b52a03ff3c7a07307377fc (patch) | |
tree | 298808433ea17b6842b87424629fa1869478ed94 | |
parent | 30c0b83490d856c1cd82441c8e2d800a88927237 (diff) | |
download | frameworks_base-d8a43f61680bacf0d4b52a03ff3c7a07307377fc.zip frameworks_base-d8a43f61680bacf0d4b52a03ff3c7a07307377fc.tar.gz frameworks_base-d8a43f61680bacf0d4b52a03ff3c7a07307377fc.tar.bz2 |
Fix issue #2047139: Remove Service.setForeground()
This API is becoming seriously abused, so now it is deprecated and has
become a no-op.
As an alternative, there is now a new API that allows you to make a service
be in the foreground but requires providing a persistent notification to
go along with this state, allowing the user to know about and control it.
-rw-r--r-- | api/current.xml | 41 | ||||
-rw-r--r-- | core/java/android/app/ActivityManagerNative.java | 23 | ||||
-rw-r--r-- | core/java/android/app/IActivityManager.java | 2 | ||||
-rw-r--r-- | core/java/android/app/Notification.java | 7 | ||||
-rw-r--r-- | core/java/android/app/NotificationManager.java | 3 | ||||
-rw-r--r-- | core/java/android/app/Service.java | 45 | ||||
-rw-r--r-- | services/java/com/android/server/NotificationManagerService.java | 74 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 45 | ||||
-rw-r--r-- | services/java/com/android/server/am/ServiceRecord.java | 66 |
9 files changed, 257 insertions, 49 deletions
diff --git a/api/current.xml b/api/current.xml index 000f5d9..e9082bc 100644 --- a/api/current.xml +++ b/api/current.xml @@ -22200,6 +22200,17 @@ visibility="public" > </field> +<field name="FLAG_FOREGROUND_SERVICE" + type="int" + transient="false" + volatile="false" + value="64" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_INSISTENT" type="int" transient="false" @@ -23760,12 +23771,40 @@ synchronized="false" static="false" final="true" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > <parameter name="isForeground" type="boolean"> </parameter> </method> +<method name="startForeground" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="int"> +</parameter> +<parameter name="notification" type="android.app.Notification"> +</parameter> +</method> +<method name="stopForeground" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="removeNotification" type="boolean"> +</parameter> +</method> <method name="stopSelf" return="void" abstract="false" diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 447512a..4dc23c0 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -39,9 +39,6 @@ import android.text.TextUtils; import android.util.Config; import android.util.Log; -import java.io.FileNotFoundException; -import java.io.FileDescriptor; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -551,8 +548,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); ComponentName className = ComponentName.readFromParcel(data); IBinder token = data.readStrongBinder(); - boolean isForeground = data.readInt() != 0; - setServiceForeground(className, token, isForeground); + int id = data.readInt(); + Notification notification = null; + if (data.readInt() != 0) { + notification = Notification.CREATOR.createFromParcel(data); + } + boolean removeNotification = data.readInt() != 0; + setServiceForeground(className, token, id, notification, removeNotification); reply.writeNoException(); return true; } @@ -1664,13 +1666,20 @@ class ActivityManagerProxy implements IActivityManager return res; } public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) throws RemoteException { + int id, Notification notification, boolean removeNotification) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); ComponentName.writeToParcel(className, data); data.writeStrongBinder(token); - data.writeInt(isForeground ? 1 : 0); + data.writeInt(id); + if (notification != null) { + data.writeInt(1); + notification.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeInt(removeNotification ? 1 : 0); mRemote.transact(SET_SERVICE_FOREGROUND_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index f6ef549..45c202d 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -139,7 +139,7 @@ public interface IActivityManager extends IInterface { public boolean stopServiceToken(ComponentName className, IBinder token, int startId) throws RemoteException; public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) throws RemoteException; + int id, Notification notification, boolean keepNotification) throws RemoteException; public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags) throws RemoteException; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a67e60b..be5a7d3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -257,6 +257,13 @@ public class Notification implements Parcelable */ public static final int FLAG_NO_CLEAR = 0x00000020; + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if this notification represents a currently running service. This + * will normally be set for you by {@link Service#startForeground}. + */ + public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; + public int flags; /** diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 39edab7..7b51fdf 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -61,7 +61,8 @@ public class NotificationManager private static INotificationManager sService; - static private INotificationManager getService() + /** @hide */ + static public INotificationManager getService() { if (sService != null) { return sService; diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index d2fb605..ce72e3f 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.res.Configuration; import android.os.RemoteException; import android.os.IBinder; +import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -304,24 +305,52 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } /** - * Control whether this service is considered to be a foreground service. + * @deprecated This is a now a no-op, use + * {@link #startForeground(int, Notification)} instead. + */ + @Deprecated + public final void setForeground(boolean isForeground) { + Log.w(TAG, "setForeground: ignoring old API call on " + getClass().getName()); + } + + /** + * Make this service run in the foreground, supplying the ongoing + * notification to be shown to the user while in this state. * By default services are background, meaning that if the system needs to * kill them to reclaim more memory (such as to display a large page in a * web browser), they can be killed without too much harm. You can set this - * flag if killing your service would be disruptive to the user: such as + * flag if killing your service would be disruptive to the user, such as * if your service is performing background music playback, so the user * would notice if their music stopped playing. * - * @param isForeground Determines whether this service is considered to - * be foreground (true) or background (false). + * @param id The identifier for this notification as per + * {@link NotificationManager#notify(int, Notification) + * NotificationManager.notify(int, Notification)}. + * @param notification The Notification to be displayed. + * + * @see #stopForeground(boolean) */ - public final void setForeground(boolean isForeground) { - if (mActivityManager == null) { - return; + public final void startForeground(int id, Notification notification) { + try { + mActivityManager.setServiceForeground( + new ComponentName(this, mClassName), mToken, id, + notification, true); + } catch (RemoteException ex) { } + } + + /** + * Remove this service from foreground state, allowing it to be killed if + * more memory is needed. + * @param keepNotification If true, the notification previously provided + * to {@link #startForeground} will remain displayed. + * @see #startForeground(int, Notification) + */ + public final void stopForeground(boolean removeNotification) { try { mActivityManager.setServiceForeground( - new ComponentName(this, mClassName), mToken, isForeground); + new ComponentName(this, mClassName), mToken, 0, null, + removeNotification); } catch (RemoteException ex) { } } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5dad8d0..d0f6eee 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -35,6 +35,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; @@ -48,6 +49,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Power; +import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.os.Vibrator; @@ -257,7 +259,8 @@ class NotificationManagerService extends INotificationManager.Stub } public void onNotificationClick(String pkg, int id) { - cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL); + cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL, + Notification.FLAG_FOREGROUND_SERVICE); } public void onPanelRevealed() { @@ -326,7 +329,7 @@ class NotificationManagerService extends INotificationManager.Stub if (pkgName == null) { return; } - cancelAllNotifications(pkgName); + cancelAllNotificationsInt(pkgName, 0, 0); } } }; @@ -580,6 +583,8 @@ class NotificationManagerService extends INotificationManager.Stub // ============================================================================ public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut) { + checkIncomingCall(pkg); + // This conditional is a dirty hack to limit the logging done on // behalf of the download manager without affecting other apps. if (!pkg.equals("com.android.providers.downloads") @@ -612,7 +617,20 @@ class NotificationManagerService extends INotificationManager.Stub } else { old = mNotificationList.remove(index); mNotificationList.add(index, r); + // Make sure we don't lose the foreground service state. + if (old != null) { + notification.flags |= + old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE; + } } + + // Ensure if this is a foreground service that the proper additional + // flags are set. + if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { + notification.flags |= Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + } + if (notification.icon != 0) { IconData icon = IconData.makeIcon(null, pkg, notification.icon, notification.iconLevel, @@ -807,9 +825,11 @@ class NotificationManagerService extends INotificationManager.Stub } /** - * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}. + * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} + * and none of the {@code mustNotHaveFlags}. */ - private void cancelNotification(String pkg, int id, int mustHaveFlags) { + private void cancelNotification(String pkg, int id, int mustHaveFlags, + int mustNotHaveFlags) { EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags); synchronized (mNotificationList) { @@ -822,6 +842,9 @@ class NotificationManagerService extends INotificationManager.Stub if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { return; } + if ((r.notification.flags & mustNotHaveFlags) != 0) { + return; + } mNotificationList.remove(index); @@ -835,7 +858,8 @@ class NotificationManagerService extends INotificationManager.Stub * Cancels all notifications from a given package that have all of the * {@code mustHaveFlags}. */ - private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) { + void cancelAllNotificationsInt(String pkg, int mustHaveFlags, + int mustNotHaveFlags) { EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags); synchronized (mNotificationList) { @@ -846,6 +870,9 @@ class NotificationManagerService extends INotificationManager.Stub if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { continue; } + if ((r.notification.flags & mustNotHaveFlags) != 0) { + continue; + } if (!r.pkg.equals(pkg)) { continue; } @@ -860,17 +887,40 @@ class NotificationManagerService extends INotificationManager.Stub } - public void cancelNotification(String pkg, int id) - { - cancelNotification(pkg, id, 0); + public void cancelNotification(String pkg, int id) { + checkIncomingCall(pkg); + // Don't allow client applications to cancel foreground service notis. + cancelNotification(pkg, id, 0, + Binder.getCallingUid() == Process.SYSTEM_UID + ? 0 : Notification.FLAG_FOREGROUND_SERVICE); } - public void cancelAllNotifications(String pkg) - { - cancelAllNotificationsInt(pkg, 0); + public void cancelAllNotifications(String pkg) { + checkIncomingCall(pkg); + + // Calling from user space, don't allow the canceling of actively + // running foreground services. + cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE); } - public void cancelAll() { + void checkIncomingCall(String pkg) { + int uid = Binder.getCallingUid(); + if (uid == Process.SYSTEM_UID || uid == 0) { + return; + } + try { + ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo( + pkg, 0); + if (ai.uid != uid) { + throw new SecurityException("Calling uid " + uid + " gave package" + + pkg + " which is owned by uid " + ai.uid); + } + } catch (PackageManager.NameNotFoundException e) { + throw new SecurityException("Unknown package " + pkg); + } + } + + void cancelAll() { synchronized (mNotificationList) { final int N = mNotificationList.size(); for (int i=N-1; i>=0; i--) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index bff9e07..2795c2a 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -39,6 +39,7 @@ import android.app.IInstrumentationWatcher; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; import android.app.Instrumentation; +import android.app.Notification; import android.app.PendingIntent; import android.app.ResultInfo; import android.backup.IBackupManager; @@ -9936,6 +9937,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } ensurePackageDexOpt(r.serviceInfo.packageName); app.thread.scheduleCreateService(r, r.serviceInfo); + r.postNotification(); created = true; } finally { if (!created) { @@ -9970,6 +9972,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (!mRestartingServices.contains(r)) { mRestartingServices.add(r); } + r.cancelNotification(); mHandler.removeCallbacks(r.restarter); mHandler.postDelayed(r.restarter, r.restartDelay); r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; @@ -10138,13 +10141,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + r.cancelNotification(); + r.isForeground = false; + r.foregroundId = 0; + r.foregroundNoti = null; + if (r.app != null) { synchronized (r.stats.getBatteryStats()) { r.stats.stopLaunchedLocked(); } r.app.services.remove(r); if (r.app.thread != null) { - updateServiceForegroundLocked(r.app, false); try { Log.i(TAG, "Stopping service: " + r.shortName); bumpServiceExecutingLocked(r); @@ -10156,6 +10163,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + r.shortName, e); serviceDoneExecutingLocked(r, true); } + updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Log.v( TAG, "Removed service that has no process: " + r.shortName); @@ -10334,20 +10342,45 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) { + int id, Notification notification, boolean removeNotification) { + final long origId = Binder.clearCallingIdentity(); + try { synchronized(this) { ServiceRecord r = findServiceLocked(className, token); if (r != null) { - if (r.isForeground != isForeground) { - final long origId = Binder.clearCallingIdentity(); - r.isForeground = isForeground; + if (id != 0) { + if (notification == null) { + throw new IllegalArgumentException("null notification"); + } + if (r.foregroundId != id) { + r.cancelNotification(); + r.foregroundId = id; + } + notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; + r.foregroundNoti = notification; + r.isForeground = true; + r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(r.app, true); } - Binder.restoreCallingIdentity(origId); + } else { + if (r.isForeground) { + r.isForeground = false; + if (r.app != null) { + updateServiceForegroundLocked(r.app, true); + } + } + if (removeNotification) { + r.cancelNotification(); + r.foregroundId = 0; + r.foregroundNoti = null; + } } } } + } finally { + Binder.restoreCallingIdentity(origId); + } } public void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index fc93b69..9318a72 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -18,12 +18,16 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; +import android.os.RemoteException; import android.os.SystemClock; import java.io.PrintWriter; @@ -63,8 +67,10 @@ class ServiceRecord extends Binder { final List<Intent> startArgs = new ArrayList<Intent>(); // start() arguments that haven't yet been delivered. - ProcessRecord app; // where this service is running or null. - boolean isForeground; // asked to run as a foreground service? + ProcessRecord app; // where this service is running or null. + boolean isForeground; // is service currently in foreground mode? + int foregroundId; // Notification ID of last foreground req. + Notification foregroundNoti; // Notification record of foreground state. long lastActivity; // last time there was some activity on the service. boolean startRequested; // someone explicitly called start? int lastStartId; // identifier of most recent start request. @@ -92,18 +98,26 @@ class ServiceRecord extends Binder { if (!resDir.equals(baseDir)) pw.print(" resDir="); pw.print(resDir); pw.print(" dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); - pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); - pw.print(" lastActivity="); pw.println(lastActivity); - pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); - pw.print(" startId="); pw.print(lastStartId); - pw.print(" executeNesting="); pw.print(executeNesting); + if (isForeground || foregroundId != 0) { + pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); + pw.print(" foregroundId="); pw.print(foregroundId); + pw.print(" foregroundNoti="); pw.println(foregroundNoti); + } + pw.print(prefix); pw.print("lastActivity="); pw.print(lastActivity); pw.print(" executingStart="); pw.print(executingStart); - pw.print(" crashCount="); pw.println(crashCount); - pw.print(prefix); pw.print("totalRestartCount="); pw.print(totalRestartCount); - pw.print(" restartCount="); pw.print(restartCount); - pw.print(" restartDelay="); pw.print(restartDelay); - pw.print(" restartTime="); pw.print(restartTime); - pw.print(" nextRestartTime="); pw.println(nextRestartTime); + pw.print(" restartTime="); pw.println(restartTime); + if (startRequested || lastStartId != 0) { + pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); + pw.print(" lastStartId="); pw.println(lastStartId); + } + if (executeNesting != 0 || crashCount != 0 || restartCount != 0 + || restartDelay != 0 || nextRestartTime != 0) { + pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting); + pw.print(" restartCount="); pw.print(restartCount); + pw.print(" restartDelay="); pw.print(restartDelay); + pw.print(" nextRestartTime="); pw.print(nextRestartTime); + pw.print(" crashCount="); pw.println(crashCount); + } if (bindings.size() > 0) { Iterator<IntentBindRecord> it = bindings.values().iterator(); while (it.hasNext()) { @@ -166,6 +180,32 @@ class ServiceRecord extends Binder { restartTime = 0; } + public void postNotification() { + if (foregroundId != 0 && foregroundNoti != null) { + INotificationManager inm = NotificationManager.getService(); + if (inm != null) { + try { + int[] outId = new int[1]; + inm.enqueueNotification(packageName, foregroundId, + foregroundNoti, outId); + } catch (RemoteException e) { + } + } + } + } + + public void cancelNotification() { + if (foregroundId != 0) { + INotificationManager inm = NotificationManager.getService(); + if (inm != null) { + try { + inm.cancelNotification(packageName, foregroundId); + } catch (RemoteException e) { + } + } + } + } + public String toString() { if (stringName != null) { return stringName; |