diff --git a/api/system-current.txt b/api/system-current.txt
index e7761b79a669f..962cbbf073861 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -163,6 +163,7 @@ package android {
field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
+ field public static final java.lang.String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
field public static final java.lang.String SERIAL_PORT = "android.permission.SERIAL_PORT";
field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
@@ -878,6 +879,7 @@ package android.content {
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
+ field public static final java.lang.String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final java.lang.String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
field public static final java.lang.String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a68136b531c42..27bbc4bca2686 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2155,10 +2155,10 @@ public class ApplicationPackageManager extends PackageManager {
public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
PersistableBundle appExtras, PersistableBundle launcherExtras,
String dialogMessage) {
- // TODO (b/75332201): Pass in the dialogMessage and use it in the interceptor dialog
try {
return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
- launcherExtras, mContext.getOpPackageName(), mContext.getUserId());
+ launcherExtras, dialogMessage, mContext.getOpPackageName(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 000912cd1e473..efc9b6da5f1c6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2281,6 +2281,28 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
+ /**
+ * Activity Action: Started to show more details about why an application was suspended.
+ *
+ *
Apps holding {@link android.Manifest.permission#SUSPEND_APPS} must declare an activity
+ * handling this intent and protect it with
+ * {@link android.Manifest.permission#SEND_SHOW_SUSPENDED_APP_DETAILS}.
+ *
+ *
Includes an extra {@link #EXTRA_PACKAGE_NAME} which is the name of the suspended package.
+ *
+ *
This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see PackageManager#isPackageSuspended()
+ * @see #ACTION_PACKAGES_SUSPENDED
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS =
+ "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
+
/**
* Broadcast Action: Sent to a package that has been unsuspended.
*
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 277738ba845a7..02ce47b85a083 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -273,8 +273,8 @@ interface IPackageManager {
void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended,
- in PersistableBundle launcherExtras, in PersistableBundle appExtras,
- String callingPackage, int userId);
+ in PersistableBundle appExtras, in PersistableBundle launcherExtras,
+ String dialogMessage, String callingPackage, int userId);
boolean isPackageSuspendedForUser(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 96aa36ce82609..a9d0911065f56 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -191,10 +191,10 @@ public abstract class PackageManagerInternal {
/**
* Retrieve launcher extras for a suspended package provided to the system in
* {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
- * PersistableBundle, String)}
+ * PersistableBundle, String)}.
*
* @param packageName The package for which to return launcher extras.
- * @param userId The user for which to check,
+ * @param userId The user for which to check.
* @return The launcher extras.
*
* @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
@@ -213,6 +213,29 @@ public abstract class PackageManagerInternal {
*/
public abstract boolean isPackageSuspended(String packageName, int userId);
+ /**
+ * Get the name of the package that suspended the given package. Packages can be suspended by
+ * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#SUSPEND_APPS}.
+ *
+ * @param suspendedPackage The package that has been suspended.
+ * @param userId The user for which to check.
+ * @return Name of the package that suspended the given package. Returns {@code null} if the
+ * given package is not currently suspended and the platform package name - i.e.
+ * {@code "android"} - if the package was suspended by a device admin.
+ */
+ public abstract String getSuspendingPackage(String suspendedPackage, int userId);
+
+ /**
+ * Get the dialog message to be shown to the user when they try to launch a suspended
+ * application.
+ *
+ * @param suspendedPackage The package that has been suspended.
+ * @param userId The user for which to check.
+ * @return The dialog message to be shown to the user.
+ */
+ public abstract String getSuspendedDialogMessage(String suspendedPackage, int userId);
+
/**
* Do a straight uid lookup for the given package/application in the given user.
* @see PackageManager#getPackageUidAsUser(String, int, int)
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f7b6e09178d7c..f471a1d9b36ca 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -34,6 +34,7 @@ import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
+import java.util.Objects;
/**
* Per-user state information about a package.
@@ -47,6 +48,7 @@ public class PackageUserState {
public boolean hidden; // Is the app restricted by owner / admin
public boolean suspended;
public String suspendingPackage;
+ public String dialogMessage; // Message to show when a suspended package launch attempt is made
public PersistableBundle suspendedAppExtras;
public PersistableBundle suspendedLauncherExtras;
public boolean instantApp;
@@ -82,6 +84,7 @@ public class PackageUserState {
hidden = o.hidden;
suspended = o.suspended;
suspendingPackage = o.suspendingPackage;
+ dialogMessage = o.dialogMessage;
suspendedAppExtras = o.suspendedAppExtras;
suspendedLauncherExtras = o.suspendedLauncherExtras;
instantApp = o.instantApp;
@@ -208,6 +211,9 @@ public class PackageUserState {
|| !suspendingPackage.equals(oldState.suspendingPackage)) {
return false;
}
+ if (!Objects.equals(dialogMessage, oldState.dialogMessage)) {
+ return false;
+ }
if (!BaseBundle.kindofEquals(suspendedAppExtras,
oldState.suspendedAppExtras)) {
return false;
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
new file mode 100644
index 0000000000000..322c876193424
--- /dev/null
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+
+public class SuspendedAppActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+ private static final String TAG = "SuspendedAppActivity";
+
+ public static final String EXTRA_DIALOG_MESSAGE = "SuspendedAppActivity.extra.DIALOG_MESSAGE";
+ public static final String EXTRA_MORE_DETAILS_INTENT =
+ "SuspendedAppActivity.extra.MORE_DETAILS_INTENT";
+
+ private Intent mMoreDetailsIntent;
+ private int mUserId;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ Window window = getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ super.onCreate(icicle);
+
+ final Intent intent = getIntent();
+ mMoreDetailsIntent = intent.getParcelableExtra(EXTRA_MORE_DETAILS_INTENT);
+ mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
+ if (mUserId < 0) {
+ Slog.wtf(TAG, "Invalid user: " + mUserId);
+ finish();
+ return;
+ }
+ String dialogMessage = intent.getStringExtra(EXTRA_DIALOG_MESSAGE);
+ if (dialogMessage == null) {
+ dialogMessage = getString(R.string.app_suspended_default_message);
+ }
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mTitle = getString(R.string.app_suspended_title);
+ ap.mMessage = String.format(getResources().getConfiguration().getLocales().get(0),
+ dialogMessage, intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ if (mMoreDetailsIntent != null) {
+ ap.mNeutralButtonText = getString(R.string.app_suspended_more_details);
+ }
+ ap.mPositiveButtonListener = ap.mNeutralButtonListener = this;
+ setupAlert();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case AlertDialog.BUTTON_NEUTRAL:
+ startActivityAsUser(mMoreDetailsIntent, UserHandle.of(mUserId));
+ Slog.i(TAG, "Started more details activity");
+ break;
+ }
+ finish();
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b7b5f232757dc..da15506ecac88 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2030,6 +2030,15 @@
+
+
+
+
+
+
+
Search
+
+ Action not allowed
+
+ The application %1$s is currently disabled.
+
+ More details
+
Turn on work profile?
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ac5c3abe14ef7..bfba8f291abe0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2877,6 +2877,10 @@
+
+
+
+
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index 2ac389d7bf301..d1808d829d383 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -32,6 +32,9 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
+import android.Manifest;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.KeyguardManager;
@@ -41,6 +44,7 @@ import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Binder;
@@ -51,6 +55,7 @@ import android.os.UserManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HarmfulAppWarningActivity;
+import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.server.LocalServices;
@@ -151,7 +156,7 @@ class ActivityStartInterceptor {
mInTask = inTask;
mActivityOptions = activityOptions;
- if (interceptSuspendPackageIfNeed()) {
+ if (interceptSuspendedPackageIfNeeded()) {
// Skip the rest of interceptions as the package is suspended by device admin so
// no user action can undo this.
return true;
@@ -204,12 +209,7 @@ class ActivityStartInterceptor {
return true;
}
- private boolean interceptSuspendPackageIfNeed() {
- // Do not intercept if the admin did not suspend the package
- if (mAInfo == null || mAInfo.applicationInfo == null ||
- (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
- return false;
- }
+ private boolean interceptSuspendedByAdminPackage() {
DevicePolicyManagerInternal devicePolicyManager = LocalServices
.getService(DevicePolicyManagerInternal.class);
if (devicePolicyManager == null) {
@@ -232,6 +232,55 @@ class ActivityStartInterceptor {
return true;
}
+ private Intent createSuspendedAppInterceptIntent(String suspendedPackage,
+ String suspendingPackage, String dialogMessage, int userId) {
+ final Intent interceptIntent = new Intent(mServiceContext, SuspendedAppActivity.class)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, suspendedPackage)
+ .putExtra(SuspendedAppActivity.EXTRA_DIALOG_MESSAGE, dialogMessage)
+ .putExtra(Intent.EXTRA_USER_ID, userId)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+ final Intent moreDetailsIntent = new Intent(Intent.ACTION_SHOW_SUSPENDED_APP_DETAILS)
+ .setPackage(suspendingPackage);
+ final String requiredPermission = Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS;
+ final ResolveInfo resolvedInfo = mSupervisor.resolveIntent(moreDetailsIntent, null, userId);
+ if (resolvedInfo != null && resolvedInfo.activityInfo != null
+ && requiredPermission.equals(resolvedInfo.activityInfo.permission)) {
+ moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, suspendedPackage)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ interceptIntent.putExtra(SuspendedAppActivity.EXTRA_MORE_DETAILS_INTENT,
+ moreDetailsIntent);
+ }
+ return interceptIntent;
+ }
+
+ private boolean interceptSuspendedPackageIfNeeded() {
+ // Do not intercept if the package is not suspended
+ if (mAInfo == null || mAInfo.applicationInfo == null ||
+ (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
+ return false;
+ }
+ final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked();
+ if (pmi == null) {
+ return false;
+ }
+ final String suspendedPackage = mAInfo.applicationInfo.packageName;
+ final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId);
+ if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+ return interceptSuspendedByAdminPackage();
+ }
+ final String dialogMessage = pmi.getSuspendedDialogMessage(suspendedPackage, mUserId);
+ mIntent = createSuspendedAppInterceptIntent(suspendedPackage, suspendingPackage,
+ dialogMessage, mUserId);
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mResolvedType = null;
+ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, 0);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+ return true;
+ }
+
private boolean interceptWorkProfileChallengeIfNeeded() {
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 33c7681456f54..26d4037b46561 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -274,6 +274,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
import com.android.internal.logging.MetricsLogger;
@@ -14006,8 +14007,8 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
- PersistableBundle appExtras, PersistableBundle launcherExtras, String callingPackage,
- int userId) {
+ PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage,
+ String callingPackage, int userId) {
try {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, null);
} catch (SecurityException e) {
@@ -14055,7 +14056,7 @@ public class PackageManagerService extends IPackageManager.Stub
unactionedPackages.add(packageName);
continue;
}
- pkgSetting.setSuspended(suspended, callingPackage, appExtras,
+ pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras,
launcherExtras, userId);
changedPackagesList.add(packageName);
}
@@ -14177,7 +14178,7 @@ public class PackageManagerService extends IPackageManager.Stub
for (int user : userIds) {
final PackageUserState pus = ps.readUserState(user);
if (pus.suspended && packageName.equals(pus.suspendingPackage)) {
- ps.setSuspended(false, null, null, null, user);
+ ps.setSuspended(false, null, null, null, null, user);
}
}
}
@@ -18911,6 +18912,7 @@ public class PackageManagerService extends IPackageManager.Stub
false /*hidden*/,
false /*suspended*/,
null, /*suspendingPackage*/
+ null, /*dialogMessage*/
null, /*suspendedAppExtras*/
null, /*suspendedLauncherExtras*/
false /*instantApp*/,
@@ -23802,6 +23804,22 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
}
+ @Override
+ public String getSuspendingPackage(String suspendedPackage, int userId) {
+ synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(suspendedPackage);
+ return (ps != null) ? ps.readUserState(userId).suspendingPackage : null;
+ }
+ }
+
+ @Override
+ public String getSuspendedDialogMessage(String suspendedPackage, int userId) {
+ synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(suspendedPackage);
+ return (ps != null) ? ps.readUserState(userId).dialogMessage : null;
+ }
+ }
+
@Override
public int getPackageUid(String packageName, int flags, int userId) {
return PackageManagerService.this
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 28e32a54090a6..a92fbb672b0ce 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1505,6 +1505,7 @@ class PackageManagerShellCommand extends ShellCommand {
private int runSuspend(boolean suspendedState) {
final PrintWriter pw = getOutPrintWriter();
int userId = UserHandle.USER_SYSTEM;
+ String dialogMessage = null;
final PersistableBundle appExtras = new PersistableBundle();
final PersistableBundle launcherExtras = new PersistableBundle();
String opt;
@@ -1513,6 +1514,9 @@ class PackageManagerShellCommand extends ShellCommand {
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "--dialogMessage":
+ dialogMessage = getNextArgRequired();
+ break;
case "--ael":
case "--aes":
case "--aed":
@@ -1553,7 +1557,7 @@ class PackageManagerShellCommand extends ShellCommand {
(Binder.getCallingUid() == Process.ROOT_UID) ? "root" : "com.android.shell";
try {
mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
- appExtras, launcherExtras, callingPackage, userId);
+ appExtras, launcherExtras, dialogMessage, callingPackage, userId);
pw.println("Package " + packageName + " new suspended state: "
+ mInterface.isPackageSuspendedForUser(packageName, userId));
return 0;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 008a81cd9cb76..fd4c5e901b7a0 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -398,11 +398,12 @@ public abstract class PackageSettingBase extends SettingBase {
return readUserState(userId).suspended;
}
- void setSuspended(boolean suspended, String suspendingPackage, PersistableBundle appExtras,
- PersistableBundle launcherExtras, int userId) {
+ void setSuspended(boolean suspended, String suspendingPackage, String dialogMessage,
+ PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
final PackageUserState existingUserState = modifyUserState(userId);
existingUserState.suspended = suspended;
existingUserState.suspendingPackage = suspended ? suspendingPackage : null;
+ existingUserState.dialogMessage = suspended ? dialogMessage : null;
existingUserState.suspendedAppExtras = suspended ? appExtras : null;
existingUserState.suspendedLauncherExtras = suspended ? launcherExtras : null;
}
@@ -425,8 +426,8 @@ public abstract class PackageSettingBase extends SettingBase {
void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
boolean notLaunched, boolean hidden, boolean suspended, String suspendingPackage,
- PersistableBundle suspendedAppExtras, PersistableBundle suspendedLauncherExtras,
- boolean instantApp,
+ String dialogMessage, PersistableBundle suspendedAppExtras,
+ PersistableBundle suspendedLauncherExtras, boolean instantApp,
boolean virtualPreload, String lastDisableAppCaller,
ArraySet enabledComponents, ArraySet disabledComponents,
int domainVerifState, int linkGeneration, int installReason,
@@ -440,6 +441,7 @@ public abstract class PackageSettingBase extends SettingBase {
state.hidden = hidden;
state.suspended = suspended;
state.suspendingPackage = suspendingPackage;
+ state.dialogMessage = dialogMessage;
state.suspendedAppExtras = suspendedAppExtras;
state.suspendedLauncherExtras = suspendedLauncherExtras;
state.lastDisableAppCaller = lastDisableAppCaller;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index d0e8544369709..898ecf3c26d2b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -222,6 +222,7 @@ public final class Settings {
private static final String ATTR_HIDDEN = "hidden";
private static final String ATTR_SUSPENDED = "suspended";
private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
+ private static final String ATTR_SUSPEND_DIALOG_MESSAGE = "suspend_dialog_message";
// Legacy, uninstall blocks are stored separately.
@Deprecated
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
@@ -734,6 +735,7 @@ public final class Settings {
false /*hidden*/,
false /*suspended*/,
null, /*suspendingPackage*/
+ null, /*dialogMessage*/
null, /*suspendedAppExtras*/
null, /*suspendedLauncherExtras*/
instantApp,
@@ -1628,6 +1630,7 @@ public final class Settings {
false /*hidden*/,
false /*suspended*/,
null, /*suspendingPackage*/
+ null, /*dialogMessage*/
null, /*suspendedAppExtras*/
null, /*suspendedLauncherExtras*/
false /*instantApp*/,
@@ -1704,6 +1707,8 @@ public final class Settings {
false);
String suspendingPackage = parser.getAttributeValue(null,
ATTR_SUSPENDING_PACKAGE);
+ final String dialogMessage = parser.getAttributeValue(null,
+ ATTR_SUSPEND_DIALOG_MESSAGE);
if (suspended && suspendingPackage == null) {
suspendingPackage = PLATFORM_PACKAGE_NAME;
}
@@ -1767,7 +1772,7 @@ public final class Settings {
setBlockUninstallLPw(userId, name, true);
}
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
- hidden, suspended, suspendingPackage, suspendedAppExtras,
+ hidden, suspended, suspendingPackage, dialogMessage, suspendedAppExtras,
suspendedLauncherExtras, instantApp, virtualPreload, enabledCaller,
enabledComponents, disabledComponents, verifState, linkGeneration,
installReason, harmfulAppWarning);
@@ -2077,7 +2082,14 @@ public final class Settings {
}
if (ustate.suspended) {
serializer.attribute(null, ATTR_SUSPENDED, "true");
- serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, ustate.suspendingPackage);
+ if (ustate.suspendingPackage != null) {
+ serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
+ ustate.suspendingPackage);
+ }
+ if (ustate.dialogMessage != null) {
+ serializer.attribute(null, ATTR_SUSPEND_DIALOG_MESSAGE,
+ ustate.dialogMessage);
+ }
if (ustate.suspendedAppExtras != null) {
serializer.startTag(null, TAG_SUSPENDED_APP_EXTRAS);
try {
@@ -4750,8 +4762,11 @@ public final class Settings {
pw.print(" suspended=");
pw.print(ps.getSuspended(user.id));
if (ps.getSuspended(user.id)) {
+ final PackageUserState pus = ps.readUserState(user.id);
pw.print(" suspendingPackage=");
- pw.print(ps.readUserState(user.id).suspendingPackage);
+ pw.print(pus.suspendingPackage);
+ pw.print(" dialogMessage=");
+ pw.print(pus.dialogMessage);
}
pw.print(" stopped=");
pw.print(ps.getStopped(user.id));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 36dc121763518..9bc5fca4a1ea8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9193,7 +9193,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
long id = mInjector.binderClearCallingIdentity();
try {
return mIPackageManager.setPackagesSuspendedAsUser(
- packageNames, suspended, null, null, "android", callingUserId);
+ packageNames, suspended, null, null, null, "android", callingUserId);
} catch (RemoteException re) {
// Shouldn't happen.
Slog.e(LOG_TAG, "Failed talking to the package manager", re);
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index cdb339ae4b92c..a85f21cda901e 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -29,6 +29,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
truth-prebuilt \
testables \
testng \
+ ub-uiautomator\
platformprotosnano
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
@@ -69,8 +70,6 @@ LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_JACK_FLAGS := --multi-dex native
LOCAL_DX_FLAGS := --multi-dex
-LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
-
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index ce98d658d329e..22bec44a8cb0b 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -63,6 +63,7 @@
+
+
+
+
+
+
+
+
result = new AtomicReference<>();
final CountDownLatch receiverLatch = new CountDownLatch(1);
-
+ final ComponentName testReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME,
+ SuspendTestReceiver.class.getCanonicalName());
final Intent broadcastIntent = new Intent(action)
- .setComponent(mTestReceiverComponent)
+ .setComponent(testReceiverComponent)
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendOrderedBroadcast(broadcastIntent, null, new BroadcastReceiver() {
@Override
@@ -175,9 +204,10 @@ public class SuspendPackagesTest {
return extras;
}
- private void suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras) {
+ private void suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras,
+ String dialogMessage) {
final String[] unchangedPackages = mPackageManager.setPackagesSuspended(
- PACKAGES_TO_SUSPEND, true, appExtras, launcherExtras, null);
+ PACKAGES_TO_SUSPEND, true, appExtras, launcherExtras, dialogMessage);
assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
}
@@ -187,6 +217,13 @@ public class SuspendPackagesTest {
assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
}
+ private void startTestAppActivity() {
+ final Intent testActivity = new Intent()
+ .setComponent(new ComponentName(TEST_APP_PACKAGE_NAME,
+ SuspendTestActivity.class.getCanonicalName()))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(testActivity);
+ }
private static boolean areSameExtras(BaseBundle expected, BaseBundle received) {
if (expected != null) {
@@ -199,13 +236,14 @@ public class SuspendPackagesTest {
}
private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) {
- assertTrue(message + ": [expected: " + expected + "; received: " + received + "]",
- areSameExtras(expected, received));
+ if (!areSameExtras(expected, received)) {
+ fail(message + ": [expected: " + expected + "; received: " + received + "]");
+ }
}
@Test
public void testIsPackageSuspended() {
- suspendTestPackage(null, null);
+ suspendTestPackage(null, null, null);
assertTrue("isPackageSuspended is false",
mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
}
@@ -217,7 +255,7 @@ public class SuspendPackagesTest {
assertNull(resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
final PersistableBundle appExtras = getExtras("testSuspendedStateFromApp", 20, "20", 0.2);
- suspendTestPackage(appExtras, null);
+ suspendTestPackage(appExtras, null, null);
resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
assertTrue("resultFromApp:suspended is false",
@@ -230,37 +268,39 @@ public class SuspendPackagesTest {
@Test
public void testMyPackageSuspendedUnsuspended() {
- mAppCommsReceiver.register(mReceiverHandler);
+ mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED,
+ ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
+ mAppCommsReceiver.drainPendingBroadcasts();
final PersistableBundle appExtras = getExtras("testMyPackageSuspendBroadcasts", 1, "1", .1);
- suspendTestPackage(appExtras, null);
+ suspendTestPackage(appExtras, null, null);
Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
+ assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
+ ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
assertSameExtras("Received app extras different to the ones supplied", appExtras,
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
unsuspendTestPackage();
intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertTrue("MY_PACKAGE_UNSUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_UNSUSPENDED.equals(intentFromApp.getAction()));
+ assertEquals("MY_PACKAGE_UNSUSPENDED delivery not reported",
+ ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
}
@Test
public void testUpdatingAppExtras() {
- mAppCommsReceiver.register(mReceiverHandler);
+ mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED);
final PersistableBundle extras1 = getExtras("testMyPackageSuspendedOnChangingExtras", 1,
"1", 0.1);
- suspendTestPackage(extras1, null);
+ suspendTestPackage(extras1, null, null);
Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
+ assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
+ ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
assertSameExtras("Received app extras different to the ones supplied", extras1,
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
"2", 0.2);
mPackageManager.setSuspendedPackageAppExtras(TEST_APP_PACKAGE_NAME, extras2);
intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
+ assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
+ ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
assertSameExtras("Received app extras different to the updated extras", extras2,
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
}
@@ -273,13 +313,27 @@ public class SuspendPackagesTest {
assertEquals(mContext.getOpPackageName(), unchangedPkgs[0]);
}
+ @Test
+ public void testActivityStoppedOnSuspend() {
+ mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_TEST_ACTIVITY_STARTED,
+ ACTION_REPORT_TEST_ACTIVITY_STOPPED);
+ startTestAppActivity();
+ Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals("Test activity start not reported",
+ ACTION_REPORT_TEST_ACTIVITY_STARTED, intentFromApp.getAction());
+ suspendTestPackage(null, null, null);
+ intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals("Test activity stop not reported on suspending the test app",
+ ACTION_REPORT_TEST_ACTIVITY_STOPPED, intentFromApp.getAction());
+ }
+
@Test
public void testGetLauncherExtrasNonNull() {
final Bundle extrasWhenUnsuspended = mLauncherApps.getSuspendedPackageLauncherExtras(
TEST_APP_PACKAGE_NAME, mContext.getUser());
assertNull("Non null extras when package unsuspended:", extrasWhenUnsuspended);
final PersistableBundle launcherExtras = getExtras("testGetLauncherExtras", 1, "1", 0.1);
- suspendTestPackage(null, launcherExtras);
+ suspendTestPackage(null, launcherExtras, null);
final Bundle receivedExtras = mLauncherApps.getSuspendedPackageLauncherExtras(
TEST_APP_PACKAGE_NAME, mContext.getUser());
assertSameExtras("Received launcher extras different to the ones supplied", launcherExtras,
@@ -288,7 +342,7 @@ public class SuspendPackagesTest {
@Test
public void testGetLauncherExtrasNull() {
- suspendTestPackage(null, null);
+ suspendTestPackage(null, null, null);
final Bundle extrasWhenNoneGiven = mLauncherApps.getSuspendedPackageLauncherExtras(
TEST_APP_PACKAGE_NAME, mContext.getUser());
assertNull("Non null extras when null extras provided:", extrasWhenNoneGiven);
@@ -339,7 +393,7 @@ public class SuspendPackagesTest {
}
};
mLauncherApps.registerCallback(mTestCallback, mReceiverHandler);
- suspendTestPackage(null, suppliedExtras);
+ suspendTestPackage(null, suppliedExtras, null);
assertFalse("Both callbacks were invoked", twoCallbackLatch.await(5, TimeUnit.SECONDS));
twoCallbackLatch.countDown();
assertTrue("No callback was invoked", twoCallbackLatch.await(2, TimeUnit.SECONDS));
@@ -373,19 +427,53 @@ public class SuspendPackagesTest {
}
};
mLauncherApps.registerCallback(mTestCallback, mReceiverHandler);
- suspendTestPackage(null, suppliedExtras);
+ suspendTestPackage(null, suppliedExtras, null);
assertTrue("Callback not invoked", oneCallbackLatch.await(5, TimeUnit.SECONDS));
final String result = overridingOneCallbackResult.get();
assertTrue("Callback did not complete as expected: " + result, result.isEmpty());
}
+ private void turnScreenOn() throws Exception {
+ if (!mUiDevice.isScreenOn()) {
+ mUiDevice.wakeUp();
+ }
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.dismissKeyguard(null, null);
+ }
+
+ @Test
+ public void testInterceptorActivity() throws Exception {
+ turnScreenOn();
+ mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED,
+ ACTION_REPORT_TEST_ACTIVITY_STARTED);
+ final String testMessage = "This is a test message";
+ suspendTestPackage(null, null, testMessage);
+ startTestAppActivity();
+ assertNull("No broadcast was expected from app", mAppCommsReceiver.pollForIntent(2));
+ assertNotNull("Given dialog message not shown",
+ mUiDevice.wait(Until.findObject(By.text(testMessage)), 5000));
+ final String buttonText = "More details";
+ final UiObject2 moreDetailsButton = mUiDevice.findObject(
+ By.clickable(true).text(buttonText));
+ assertNotNull("\"More Details\" button not shown", moreDetailsButton);
+ moreDetailsButton.click();
+ final Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals("\"More Details\" activity start not reported",
+ ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED, intentFromApp.getAction());
+ final String receivedPackageName = intentFromApp.getStringExtra(
+ EXTRA_RECEIVED_PACKAGE_NAME);
+ assertEquals("Wrong package name received by \"More Details\" activity",
+ TEST_APP_PACKAGE_NAME, receivedPackageName);
+ }
+
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
mAppCommsReceiver.unregister();
if (mTestCallback != null) {
mLauncherApps.unregisterCallback(mTestCallback);
}
- Thread.sleep(250); // To prevent any race with the next registerReceiver
+ mContext.sendBroadcast(new Intent(ACTION_FINISH_TEST_ACTIVITY)
+ .setPackage(TEST_APP_PACKAGE_NAME));
}
private static abstract class StubbedCallback extends LauncherApps.Callback {
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendedDetailsActivity.java b/services/tests/servicestests/src/com/android/server/pm/SuspendedDetailsActivity.java
new file mode 100644
index 0000000000000..18f01231e194c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendedDetailsActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED;
+import static com.android.server.pm.SuspendPackagesTest.EXTRA_RECEIVED_PACKAGE_NAME;
+import static com.android.server.pm.SuspendPackagesTest.INSTRUMENTATION_PACKAGE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class SuspendedDetailsActivity extends Activity {
+ private static final String TAG = SuspendedDetailsActivity.class.getSimpleName();
+
+ @Override
+ protected void onStart() {
+ Log.d(TAG, "onStart");
+ final String suspendedPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ super.onStart();
+ final Intent reportStart = new Intent(ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED)
+ .putExtra(EXTRA_RECEIVED_PACKAGE_NAME, suspendedPackage)
+ .setPackage(INSTRUMENTATION_PACKAGE);
+ sendBroadcast(reportStart);
+ finish();
+ }
+}
diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk b/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk
index afdde727b7ef5..ae0b0f9326d07 100644
--- a/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk
+++ b/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk
@@ -20,7 +20,7 @@ LOCAL_MODULE_TAGS := tests
LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_SRC_FILES += ../../src/com/android/server/pm/SuspendPackagesTest.java
diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestActivity.java b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestActivity.java
index fa5fc5863a0e8..ab7ddbbe2f47a 100644
--- a/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestActivity.java
+++ b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestActivity.java
@@ -16,9 +16,45 @@
package com.android.servicestests.apps.suspendtestapp;
+import static com.android.server.pm.SuspendPackagesTest.ACTION_FINISH_TEST_ACTIVITY;
+import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_TEST_ACTIVITY_STARTED;
+import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_TEST_ACTIVITY_STOPPED;
+import static com.android.server.pm.SuspendPackagesTest.INSTRUMENTATION_PACKAGE;
+
import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
public class SuspendTestActivity extends Activity {
private static final String TAG = SuspendTestActivity.class.getSimpleName();
-}
\ No newline at end of file
+ private BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Finishing test activity from receiver");
+ SuspendTestActivity.this.finish();
+ }
+ };
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "onStart");
+ final Intent reportStart = new Intent(ACTION_REPORT_TEST_ACTIVITY_STARTED)
+ .setPackage(INSTRUMENTATION_PACKAGE);
+ sendBroadcast(reportStart);
+ registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_TEST_ACTIVITY));
+ super.onStart();
+ }
+ @Override
+ public void onStop() {
+ Log.d(TAG, "onStop");
+ final Intent reportStop = new Intent(ACTION_REPORT_TEST_ACTIVITY_STOPPED)
+ .setPackage(INSTRUMENTATION_PACKAGE);
+ sendBroadcast(reportStop);
+ unregisterReceiver(mFinishReceiver);
+ super.onStop();
+ }
+}