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 79047c62b5)
This commit is contained in:
Christopher Tate
2017-03-21 11:37:06 -07:00
committed by Chris Tate
parent 217ecd0729
commit 08992ac57e
19 changed files with 265 additions and 50 deletions

View File

@@ -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;

View File

@@ -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(

View File

@@ -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,

View File

@@ -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);

View File

@@ -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());

View File

@@ -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).
@@ -2587,12 +2587,48 @@ public abstract class Context {
@Nullable
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

View File

@@ -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);

View File

@@ -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.
*/