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(); + } +}