summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.xml41
-rw-r--r--core/java/android/app/ActivityManagerNative.java23
-rw-r--r--core/java/android/app/IActivityManager.java2
-rw-r--r--core/java/android/app/Notification.java7
-rw-r--r--core/java/android/app/NotificationManager.java3
-rw-r--r--core/java/android/app/Service.java45
-rw-r--r--services/java/com/android/server/NotificationManagerService.java74
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java45
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java66
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;