From d8a43f61680bacf0d4b52a03ff3c7a07307377fc Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 17 Aug 2009 23:33:56 -0700 Subject: [PATCH] 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. --- api/current.xml | 41 +++++++++- .../android/app/ActivityManagerNative.java | 23 ++++-- core/java/android/app/IActivityManager.java | 2 +- core/java/android/app/Notification.java | 7 ++ .../java/android/app/NotificationManager.java | 3 +- core/java/android/app/Service.java | 47 +++++++++--- .../server/NotificationManagerService.java | 74 ++++++++++++++++--- .../server/am/ActivityManagerService.java | 45 +++++++++-- .../com/android/server/am/ServiceRecord.java | 66 +++++++++++++---- 9 files changed, 258 insertions(+), 50 deletions(-) diff --git a/api/current.xml b/api/current.xml index 000f5d9c6ff4c..e9082bc5af253 100644 --- a/api/current.xml +++ b/api/current.xml @@ -22200,6 +22200,17 @@ visibility="public" > + + + + + + + + + + + + =0; i--) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index bff9e07cfc974..2795c2a134841 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 fc93b697b0448..9318a7223b20c 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 startArgs = new ArrayList(); // 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 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;