diff options
| author | Christopher Tate <ctate@google.com> | 2017-03-21 11:37:06 -0700 |
|---|---|---|
| committer | Chris Tate <ctate@android.com> | 2017-03-30 18:31:24 +0000 |
| commit | 08992ac57e973d6bf32693725ebb341a481e5944 (patch) | |
| tree | b766c88e226f2b7705993eeef88984f4a04f5183 /core/java/android | |
| parent | 217ecd0729809508c731dc5f49b437fd9681b65e (diff) | |
API refactor: context.startForegroundService()
Rather than require an a-priori Notification be supplied in order to
start a service directly into the foreground state, we adopt a two-stage
compound operation for undertaking ongoing service work even from a
background execution state. Context#startForegroundService() is not
subject to background restrictions, with the requirement that the
service formally enter the foreground state via startForeground() within
5 seconds. If the service does not do so, it is stopped by the OS and
the app is blamed with a service ANR.
We also introduce a new flavor of PendingIntent that starts a service
into this two-stage "promises to call startForeground()" sequence, so
that deferred and second-party launches can take advantage of it.
Bug 36130212
Test: CTS
Change-Id: I96d6b23fcfc27d8fa606827b7d48a093611b2345
(cherry picked from commit 79047c62b58fb0a0ddf28e2b90fe4d17e05bc528)
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/ActivityManager.java | 7 | ||||
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 27 | ||||
| -rw-r--r-- | core/java/android/app/IActivityManager.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/app/NotificationManager.java | 3 | ||||
| -rw-r--r-- | core/java/android/app/PendingIntent.java | 38 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 44 | ||||
| -rw-r--r-- | core/java/android/content/ContextWrapper.java | 13 | ||||
| -rw-r--r-- | core/java/android/view/ContextThemeWrapper.java | 4 |
8 files changed, 123 insertions, 15 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9f2f669b64c8..4004bd6686b1 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -405,6 +405,13 @@ public class ActivityManager { */ public static final int INTENT_SENDER_SERVICE = 4; + /** + * Type for IActivityManaqer.getIntentSender: this PendingIntent is + * for a startForegroundService operation. + * @hide + */ + public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5; + /** @hide User operation call: success! */ public static final int USER_OP_SUCCESS = 0; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4c080c9e6a95..467ba996ad4f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1447,14 +1447,21 @@ class ContextImpl extends Context { @Override public ComponentName startService(Intent service) { warnIfCallingFromSystemProcess(); - return startServiceCommon(service, -1, null, mUser); + return startServiceCommon(service, -1, null, false, mUser); } @Override + public ComponentName startForegroundService(Intent service) { + warnIfCallingFromSystemProcess(); + return startServiceCommon(service, -1, null, true, mUser); + } + + // STOPSHIP: remove when NotificationManager.startServiceInForeground() is retired + @Override public ComponentName startServiceInForeground(Intent service, int id, Notification notification) { warnIfCallingFromSystemProcess(); - return startServiceCommon(service, id, notification, mUser); + return startServiceCommon(service, id, notification, false, mUser); } @Override @@ -1465,24 +1472,30 @@ class ContextImpl extends Context { @Override public ComponentName startServiceAsUser(Intent service, UserHandle user) { - return startServiceCommon(service, -1, null, user); + return startServiceCommon(service, -1, null, false, user); } @Override + public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) { + return startServiceCommon(service, -1, null, true, user); + } + + // STOPSHIP: remove when NotificationManager.startServiceInForeground() is retired + @Override public ComponentName startServiceInForegroundAsUser(Intent service, int id, Notification notification, UserHandle user) { - return startServiceCommon(service, id, notification, user); + return startServiceCommon(service, id, notification, false, user); } private ComponentName startServiceCommon(Intent service, int id, Notification notification, - UserHandle user) { + boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( - getContentResolver()), id, notification, getOpPackageName(), - user.getIdentifier()); + getContentResolver()), id, notification, requireForeground, + getOpPackageName(), user.getIdentifier()); if (cn != null) { if (cn.getPackageName().equals("!")) { throw new SecurityException( diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index b9d1d91fabda..0a5e4bef8dca 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -130,7 +130,7 @@ interface IActivityManager { PendingIntent getRunningServiceControlPanel(in ComponentName service); ComponentName startService(in IApplicationThread caller, in Intent service, in String resolvedType, int id, in Notification notification, - in String callingPackage, int userId); + boolean requireForeground, in String callingPackage, int userId); int stopService(in IApplicationThread caller, in Intent service, in String resolvedType, int userId); int bindService(in IApplicationThread caller, in IBinder token, in Intent service, diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 75998f2eb36d..72c59781ae80 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1171,8 +1171,11 @@ public class NotificationManager * @return If the service is being started or is already running, the * {@link ComponentName} of the actual service that was started is * returned; else if the service does not exist null is returned. + * + * @deprecated STOPSHIP transition away from this for O */ @Nullable + @Deprecated public ComponentName startServiceInForeground(Intent service, int id, Notification notification) { return mContext.startServiceInForeground(service, id, notification); diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 7d1a16ab1045..dc432afb5c06 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -596,6 +596,42 @@ public final class PendingIntent implements Parcelable { */ public static PendingIntent getService(Context context, int requestCode, @NonNull Intent intent, @Flags int flags) { + return buildServicePendingIntent(context, requestCode, intent, flags, + ActivityManager.INTENT_SENDER_SERVICE); + } + + /** + * Retrieve a PendingIntent that will start a foreground service, like calling + * {@link Context#startService Context.startForegroundService()}. The start + * arguments given to the service will come from the extras of the Intent. + * + * <p class="note">For security reasons, the {@link android.content.Intent} + * you supply here should almost always be an <em>explicit intent</em>, + * that is specify an explicit component to be delivered to through + * {@link Intent#setClass(android.content.Context, Class) Intent.setClass}</p> + * + * @param context The Context in which this PendingIntent should start + * the service. + * @param requestCode Private request code for the sender + * @param intent An Intent describing the service to be started. + * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, + * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, + * {@link #FLAG_IMMUTABLE} or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if {@link #FLAG_NO_CREATE} has been + * supplied. + */ + public static PendingIntent getForegroundService(Context context, int requestCode, + @NonNull Intent intent, @Flags int flags) { + return buildServicePendingIntent(context, requestCode, intent, flags, + ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE); + } + + private static PendingIntent buildServicePendingIntent(Context context, int requestCode, + Intent intent, int flags, int serviceKind) { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; @@ -603,7 +639,7 @@ public final class PendingIntent implements Parcelable { intent.prepareToLeaveProcess(context); IIntentSender target = ActivityManager.getService().getIntentSender( - ActivityManager.INTENT_SENDER_SERVICE, packageName, + serviceKind, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, null, UserHandle.myUserId()); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3a8a4206e105..1803bbe24610 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2576,7 +2576,7 @@ public abstract class Context { * {@link ComponentName} of the actual service that was started is * returned; else if the service does not exist null is returned. * - * @throws SecurityException If the caller does not permission to access the service + * @throws SecurityException If the caller does not have permission to access the service * or the service can not be found. * @throws IllegalStateException If the application is in a state where the service * can not be started (such as not in the foreground in a state when services are allowed). @@ -2588,11 +2588,47 @@ public abstract class Context { public abstract ComponentName startService(Intent service); /** + * Similar to {@link #startService(Intent)}, but with an implicit promise that the + * Service will call {@link android.app.Service#startForeground(int, Notification) + * startForeground(int, Notification)} once it begins running. The service is given + * an amount of time comparable to the ANR interval to do this, otherwise the system + * will automatically stop the service and declare the app ANR. + * + * <p>Unlike the ordinary {@link #startService(Intent)}, this method can be used + * at any time, regardless of whether the app hosting the service is in a foreground + * state. + * + * @param service Identifies the service to be started. The Intent must be + * fully explicit (supplying a component name). Additional values + * may be included in the Intent extras to supply arguments along with + * this specific start call. + * + * @return If the service is being started or is already running, the + * {@link ComponentName} of the actual service that was started is + * returned; else if the service does not exist null is returned. + * + * @throws SecurityException If the caller does not have permission to access the service + * or the service can not be found. + * + * @see #stopService + * @see android.app.Service#startForeground(int, Notification) + */ + @Nullable + public abstract ComponentName startForegroundService(Intent service); + + /** + * @hide like {@link #startForegroundService(Intent)} but for a specific user. + */ + @Nullable + public abstract ComponentName startForegroundServiceAsUser(Intent service, UserHandle user); + + /** * Start a service directly into the "foreground service" state. Unlike {@link #startService}, * this method can be used from within background operations like broadcast receivers * or scheduled jobs. The API entry point for this is in NotificationManager in order to * preserve appropriate public package layering. * @hide + * @deprecated STOPSHIP remove in favor of two-step startForegroundService() + startForeground() */ @Nullable public abstract ComponentName startServiceInForeground(Intent service, @@ -2620,7 +2656,7 @@ public abstract class Context { * @return If there is a service matching the given Intent that is already * running, then it is stopped and {@code true} is returned; else {@code false} is returned. * - * @throws SecurityException If the caller does not permission to access the service + * @throws SecurityException If the caller does not have permission to access the service * or the service can not be found. * @throws IllegalStateException If the application is in a state where the service * can not be started (such as not in the foreground in a state when services are allowed). @@ -2638,7 +2674,9 @@ public abstract class Context { /** * @hide like {@link #startServiceInForeground(Intent, int, Notification)} * but for a specific user. + * @deprecated STOPSHIP remove when trial API is turned off */ + @Deprecated @Nullable public abstract ComponentName startServiceInForegroundAsUser(Intent service, int id, Notification notification, UserHandle user); @@ -2685,7 +2723,7 @@ public abstract class Context { * {@code false} is returned if the connection is not made so you will not * receive the service object. * - * @throws SecurityException If the caller does not permission to access the service + * @throws SecurityException If the caller does not have permission to access the service * or the service can not be found. * * @see #unbindService diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6b0bbfaedc3e..75784a69c74c 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -644,7 +644,12 @@ public class ContextWrapper extends Context { return mBase.startService(service); } - /** @hide */ + @Override + public ComponentName startForegroundService(Intent service) { + return mBase.startForegroundService(service); + } + + /** @hide STOPSHIP remove when trial API is turned down */ @Override public ComponentName startServiceInForeground(Intent service, int id, Notification notification) { @@ -664,6 +669,12 @@ public class ContextWrapper extends Context { /** @hide */ @Override + public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) { + return mBase.startForegroundServiceAsUser(service, user); + } + + /** @hide STOPSHIP removed when trial API is turned down */ + @Override public ComponentName startServiceInForegroundAsUser(Intent service, int id, Notification notification, UserHandle user) { return mBase.startServiceInForegroundAsUser(service, id, notification, user); diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 86318e91b885..d3cc175d3e1c 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -36,8 +36,8 @@ public class ContextThemeWrapper extends ContextWrapper { /** * Creates a new context wrapper with no theme and no base context. - * <p> - * <stong>Note:</strong> A base context <strong>must</strong> be attached + * <p class="note"> + * <strong>Note:</strong> A base context <strong>must</strong> be attached * using {@link #attachBaseContext(Context)} before calling any other * method on the newly constructed context wrapper. */ |
