summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2017-03-21 11:37:06 -0700
committerChris Tate <ctate@android.com>2017-03-30 18:31:24 +0000
commit08992ac57e973d6bf32693725ebb341a481e5944 (patch)
treeb766c88e226f2b7705993eeef88984f4a04f5183 /core/java/android
parent217ecd0729809508c731dc5f49b437fd9681b65e (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.java7
-rw-r--r--core/java/android/app/ContextImpl.java27
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/app/NotificationManager.java3
-rw-r--r--core/java/android/app/PendingIntent.java38
-rw-r--r--core/java/android/content/Context.java44
-rw-r--r--core/java/android/content/ContextWrapper.java13
-rw-r--r--core/java/android/view/ContextThemeWrapper.java4
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.
*/