diff options
27 files changed, 891 insertions, 68 deletions
diff --git a/api/current.txt b/api/current.txt index 7439d1d04ad9..162b6f6d0fb7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11167,6 +11167,7 @@ package android.content.pm { method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int); + method public android.os.PersistableBundle getSuspendedPackageAppExtras(); method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures(); method public abstract java.lang.String[] getSystemSharedLibraryNames(); method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo); @@ -11180,6 +11181,7 @@ package android.content.pm { method public abstract boolean hasSystemFeature(java.lang.String, int); method public abstract boolean isInstantApp(); method public abstract boolean isInstantApp(java.lang.String); + method public boolean isPackageSuspended(); method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public abstract boolean isSafeMode(); method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); diff --git a/api/system-current.txt b/api/system-current.txt index 04d78a19e442..8aae6009f2a8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -181,6 +181,7 @@ package android { field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR"; field public static final java.lang.String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final java.lang.String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; + field public static final java.lang.String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final java.lang.String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; field public static final java.lang.String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE"; field public static final java.lang.String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; @@ -1017,15 +1018,19 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String); method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int); method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); + method public android.os.PersistableBundle getSuspendedPackageAppExtras(java.lang.String); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public boolean isPackageSuspended(java.lang.String); method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback); method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence); + method public java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String); + method public void setSuspendedPackageAppExtras(java.lang.String, android.os.PersistableBundle); method public abstract void setUpdateAvailable(java.lang.String, boolean); method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int); method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index b8c4ef783a60..fb8ded1e7a43 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -70,6 +70,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -2151,16 +2152,42 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, - int userId) { + 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, userId); + return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras, + launcherExtras, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @Override + public PersistableBundle getSuspendedPackageAppExtras(String packageName) { + try { + return mPM.getPackageSuspendedAppExtras(packageName, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public PersistableBundle getSuspendedPackageAppExtras() { + return getSuspendedPackageAppExtras(mContext.getOpPackageName()); + } + + @Override + public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) { + try { + mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override public boolean isPackageSuspendedForUser(String packageName, int userId) { try { return mPM.isPackageSuspendedForUser(packageName, userId); @@ -2171,6 +2198,17 @@ public class ApplicationPackageManager extends PackageManager { /** @hide */ @Override + public boolean isPackageSuspended(String packageName) { + return isPackageSuspendedForUser(packageName, mContext.getUserId()); + } + + @Override + public boolean isPackageSuspended() { + return isPackageSuspendedForUser(mContext.getOpPackageName(), mContext.getUserId()); + } + + /** @hide */ + @Override public void setApplicationCategoryHint(String packageName, int categoryHint) { try { mPM.setApplicationCategoryHint(packageName, categoryHint, diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 36a74a489890..f4352f976316 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -50,8 +50,8 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.IArtManager; import android.graphics.Bitmap; import android.net.Uri; -import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.content.IntentSender; /** @@ -272,9 +272,17 @@ interface IPackageManager { void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage); - String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended, int userId); + String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended, + in PersistableBundle launcherExtras, in PersistableBundle appExtras, + String callingPackage, int userId); + boolean isPackageSuspendedForUser(String packageName, int userId); + PersistableBundle getPackageSuspendedAppExtras(String pacakgeName, int userId); + + void setSuspendedPackageAppExtras(String packageName, in PersistableBundle appExtras, + int userId); + /** * Backup/restore support - only the system uid may use these. */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3536eea85423..4d8773c27d93 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -51,6 +51,7 @@ import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -5510,28 +5511,49 @@ public abstract class PackageManager { /** * Puts the package in a suspended state, where attempts at starting activities are denied. * - * <p>It doesn't remove the data or the actual package file. The application notifications - * will be hidden, the application will not show up in recents, will not be able to show - * toasts or dialogs or ring the device. + * <p>It doesn't remove the data or the actual package file. The application's notifications + * will be hidden, any of the it's started activities will be stopped and it will not be able to + * show toasts or dialogs or ring the device. When the user tries to launch a suspended app, a + * system dialog with the given {@code dialogMessage} will be shown instead.</p> * * <p>The package must already be installed. If the package is uninstalled while suspended - * the package will no longer be suspended. + * the package will no longer be suspended. </p> + * + * <p>Optionally, the suspending app can provide extra information in the form of + * {@link PersistableBundle} objects to be shared with the apps being suspended and the + * launcher to support customization that they might need to handle the suspended state. </p> + * + * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} or + * {@link Manifest.permission#MANAGE_USERS} to use this api.</p> * * @param packageNames The names of the packages to set the suspended status. * @param suspended If set to {@code true} than the packages will be suspended, if set to - * {@code false} the packages will be unsuspended. - * @param userId The user id. + * {@code false}, the packages will be unsuspended. + * @param appExtras An optional {@link PersistableBundle} that the suspending app can provide + * which will be shared with the apps being suspended. Ignored if + * {@code suspended} is false. + * @param launcherExtras An optional {@link PersistableBundle} that the suspending app can + * provide which will be shared with the launcher. Ignored if + * {@code suspended} is false. + * @param dialogMessage The message to be displayed to the user, when they try to launch a + * suspended app. * * @return an array of package names for which the suspended status is not set as requested in * this method. * * @hide */ - public abstract String[] setPackagesSuspendedAsUser( - String[] packageNames, boolean suspended, @UserIdInt int userId); + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.SUSPEND_APPS, + Manifest.permission.MANAGE_USERS}) + public String[] setPackagesSuspended(String[] packageNames, boolean suspended, + @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, + String dialogMessage) { + throw new UnsupportedOperationException("setPackagesSuspended not implemented"); + } /** - * @see #setPackageSuspendedAsUser(String, boolean, int) + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) * @param packageName The name of the package to get the suspended status of. * @param userId The user id. * @return {@code true} if the package is suspended or {@code false} if the package is not @@ -5541,6 +5563,86 @@ public abstract class PackageManager { public abstract boolean isPackageSuspendedForUser(String packageName, int userId); /** + * Query if an app is currently suspended. + * + * @return {@code true} if the given package is suspended, {@code false} otherwise + * + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) + * @hide + */ + @SystemApi + public boolean isPackageSuspended(String packageName) { + throw new UnsupportedOperationException("isPackageSuspended not implemented"); + } + + /** + * Apps can query this to know if they have been suspended. + * + * @return {@code true} if the calling package has been suspended, {@code false} otherwise. + * + * @see #getSuspendedPackageAppExtras() + */ + public boolean isPackageSuspended() { + throw new UnsupportedOperationException("isPackageSuspended not implemented"); + } + + /** + * Retrieve the {@link PersistableBundle} that was passed as {@code appExtras} when the given + * package was suspended. + * + * <p> The caller must hold permission {@link Manifest.permission#SUSPEND_APPS} to use this + * api.</p> + * + * @param packageName The package to retrieve extras for. + * @return The {@code appExtras} for the suspended package. + * + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SUSPEND_APPS) + public PersistableBundle getSuspendedPackageAppExtras(String packageName) { + throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented"); + } + + /** + * Set the app extras for a suspended package. This method can be used to update the appExtras + * for a package that was earlier suspended using + * {@link #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, + * String)} + * Does nothing if the given package is not already in a suspended state. + * + * @param packageName The package for which the appExtras need to be updated + * @param appExtras The new appExtras for the given package + * + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SUSPEND_APPS) + public void setSuspendedPackageAppExtras(String packageName, + @Nullable PersistableBundle appExtras) { + throw new UnsupportedOperationException("setSuspendedPackageAppExtras not implemented"); + } + + /** + * Returns any extra information supplied as {@code appExtras} to the system when the calling + * app was suspended. + * + * <p> Note: This just returns whatever {@link PersistableBundle} was passed to the system via + * {@code setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, + * String)} when suspending the package, <em> which might be {@code null}. </em></p> + * + * @return A {@link PersistableBundle} containing the extras for the app, or {@code null} if the + * package is not currently suspended. + * @see #isPackageSuspended() + */ + public @Nullable PersistableBundle getSuspendedPackageAppExtras() { + throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented"); + } + + /** * Provide a hint of what the {@link ApplicationInfo#category} value should * be for the given package. * <p> diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 293beb2bf7bb..f7b6e09178d7 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -27,6 +27,8 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import android.os.BaseBundle; +import android.os.PersistableBundle; import android.util.ArraySet; import com.android.internal.util.ArrayUtils; @@ -44,6 +46,9 @@ public class PackageUserState { public boolean notLaunched; public boolean hidden; // Is the app restricted by owner / admin public boolean suspended; + public String suspendingPackage; + public PersistableBundle suspendedAppExtras; + public PersistableBundle suspendedLauncherExtras; public boolean instantApp; public boolean virtualPreload; public int enabled; @@ -76,6 +81,9 @@ public class PackageUserState { notLaunched = o.notLaunched; hidden = o.hidden; suspended = o.suspended; + suspendingPackage = o.suspendingPackage; + suspendedAppExtras = o.suspendedAppExtras; + suspendedLauncherExtras = o.suspendedLauncherExtras; instantApp = o.instantApp; virtualPreload = o.virtualPreload; enabled = o.enabled; @@ -195,6 +203,20 @@ public class PackageUserState { if (suspended != oldState.suspended) { return false; } + if (suspended) { + if (suspendingPackage == null + || !suspendingPackage.equals(oldState.suspendingPackage)) { + return false; + } + if (!BaseBundle.kindofEquals(suspendedAppExtras, + oldState.suspendedAppExtras)) { + return false; + } + if (!BaseBundle.kindofEquals(suspendedLauncherExtras, + oldState.suspendedLauncherExtras)) { + return false; + } + } if (instantApp != oldState.instantApp) { return false; } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 5312dcaeb508..f5a7433e9739 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -357,6 +357,23 @@ public class BaseBundle { } /** + * Does a loose equality check between two given {@link BaseBundle} objects. + * Returns {@code true} if both are {@code null}, or if both are equal as per + * {@link #kindofEquals(BaseBundle)} + * + * @param a A {@link BaseBundle} object + * @param b Another {@link BaseBundle} to compare with a + * @return {@code true} if both are the same, {@code false} otherwise + * + * @see #kindofEquals(BaseBundle) + * + * @hide + */ + public static boolean kindofEquals(BaseBundle a, BaseBundle b) { + return (a == b) || (a != null && a.kindofEquals(b)); + } + + /** * @hide This kind-of does an equality comparison. Kind-of. */ public boolean kindofEquals(BaseBundle other) { diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index f8050a15e78e..88bb4a6f4295 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -110,6 +110,7 @@ message PackageProto { optional bool is_launched = 6; optional EnabledState enabled_state = 7; optional string last_disabled_app_caller = 8; + optional string suspending_package = 9; } // Name of package. e.g. "com.android.providers.telephony". diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2ebffb7265d7..97b80ff1d894 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1441,6 +1441,13 @@ android:label="@string/permlab_bluetooth" android:protectionLevel="normal" /> + <!-- @SystemApi Allows an application to suspend other apps, which will prevent the user + from using them until they are unsuspended. + @hide + --> + <permission android:name="android.permission.SUSPEND_APPS" + android:protectionLevel="signature|privileged" /> + <!-- Allows applications to discover and pair bluetooth devices. <p>Protection level: normal --> diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index fc8b624f6b7e..1335ced6182e 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -508,6 +508,10 @@ class RecentTasks { && tr.userId == userId && tr.realActivitySuspended != suspended) { tr.realActivitySuspended = suspended; + if (suspended) { + mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, + REMOVE_FROM_RECENTS, "suspended-package"); + } notifyTaskPersisterLocked(tr, false); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 26e9a9a287af..576d228a78ac 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -23,7 +23,6 @@ import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; -import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; @@ -98,7 +97,6 @@ import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; import static com.android.server.pm.InstructionSets.getPreferredInstructionSet; import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet; -import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter; import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures; import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists; @@ -214,6 +212,7 @@ import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PatternMatcher; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -238,7 +237,6 @@ import android.provider.Settings.Secure; import android.security.KeyStore; import android.security.SystemKeyStore; import android.service.pm.PackageServiceDumpProto; -import android.service.textclassifier.TextClassifierService; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -407,7 +405,7 @@ public class PackageManagerService extends IPackageManager.Stub static final boolean DEBUG_DOMAIN_VERIFICATION = false; private static final boolean DEBUG_BACKUP = false; public static final boolean DEBUG_INSTALL = false; - public static final boolean DEBUG_REMOVE = false; + public static final boolean DEBUG_REMOVE = true; private static final boolean DEBUG_BROADCASTS = false; private static final boolean DEBUG_SHOW_INFO = false; private static final boolean DEBUG_PACKAGE_INFO = false; @@ -13964,29 +13962,45 @@ public class PackageManagerService extends IPackageManager.Stub @Override public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, String callingPackage, int userId) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); + try { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, null); + } catch (SecurityException e) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_USERS, + "Callers need to have either " + Manifest.permission.SUSPEND_APPS + " or " + + Manifest.permission.MANAGE_USERS); + } final int callingUid = Binder.getCallingUid(); mPermissionManager.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, true /* checkShell */, "setPackagesSuspended for user " + userId); + if (callingUid != Process.ROOT_UID && + !UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) { + throw new IllegalArgumentException("callingPackage " + callingPackage + " does not" + + " belong to calling app id " + UserHandle.getAppId(callingUid)); + } if (ArrayUtils.isEmpty(packageNames)) { return packageNames; } // List of package names for whom the suspended state has changed. - List<String> changedPackages = new ArrayList<>(packageNames.length); + final List<String> changedPackages = new ArrayList<>(packageNames.length); // List of package names for whom the suspended state is not set as requested in this // method. - List<String> unactionedPackages = new ArrayList<>(packageNames.length); - long callingId = Binder.clearCallingIdentity(); + final List<String> unactionedPackages = new ArrayList<>(packageNames.length); + final long callingId = Binder.clearCallingIdentity(); try { - for (int i = 0; i < packageNames.length; i++) { - String packageName = packageNames[i]; - boolean changed = false; - final int appId; - synchronized (mPackages) { + synchronized (mPackages) { + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + if (packageName == callingPackage) { + Slog.w(TAG, "Calling package: " + callingPackage + "trying to " + + (suspended ? "" : "un") + "suspend itself. Ignoring"); + unactionedPackages.add(packageName); + continue; + } final PackageSetting pkgSetting = mSettings.mPackages.get(packageName); if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) { @@ -13995,42 +14009,75 @@ public class PackageManagerService extends IPackageManager.Stub unactionedPackages.add(packageName); continue; } - appId = pkgSetting.appId; if (pkgSetting.getSuspended(userId) != suspended) { if (!canSuspendPackageForUserLocked(packageName, userId)) { unactionedPackages.add(packageName); continue; } - pkgSetting.setSuspended(suspended, userId); - mSettings.writePackageRestrictionsLPr(userId); - changed = true; + pkgSetting.setSuspended(suspended, callingPackage, appExtras, + launcherExtras, userId); changedPackages.add(packageName); } } - - if (changed && suspended) { - killApplication(packageName, UserHandle.getUid(userId, appId), - "suspending package"); - } } } finally { Binder.restoreCallingIdentity(callingId); } - + // TODO (b/75036698): Also send each package a broadcast when suspended state changed if (!changedPackages.isEmpty()) { sendPackagesSuspendedForUser(changedPackages.toArray( new String[changedPackages.size()]), userId, suspended); + synchronized (mPackages) { + scheduleWritePackageRestrictionsLocked(userId); + } } return unactionedPackages.toArray(new String[unactionedPackages.size()]); } @Override + public PersistableBundle getPackageSuspendedAppExtras(String packageName, int userId) { + final int callingUid = Binder.getCallingUid(); + if (getPackageUid(packageName, 0, userId) != callingUid) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null); + } + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) { + throw new IllegalArgumentException("Unknown target package: " + packageName); + } + final PackageUserState packageUserState = ps.readUserState(userId); + return packageUserState.suspended ? packageUserState.suspendedAppExtras : null; + } + } + + @Override + public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras, + int userId) { + final int callingUid = Binder.getCallingUid(); + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null); + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) { + throw new IllegalArgumentException("Unknown target package: " + packageName); + } + final PackageUserState packageUserState = ps.readUserState(userId); + if (packageUserState.suspended) { + // TODO (b/75036698): Also send this package a broadcast with the new app extras + packageUserState.suspendedAppExtras = appExtras; + } + } + } + + @Override public boolean isPackageSuspendedForUser(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); mPermissionManager.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, false /* checkShell */, "isPackageSuspendedForUser for user " + userId); + if (getPackageUid(packageName, 0, userId) != callingUid) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null); + } synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) { @@ -14040,6 +14087,21 @@ public class PackageManagerService extends IPackageManager.Stub } } + void onSuspendingPackageRemoved(String packageName, int userId) { + final int[] userIds = (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds() + : new int[] {userId}; + synchronized (mPackages) { + for (PackageSetting ps : mSettings.mPackages.values()) { + for (int user : userIds) { + final PackageUserState pus = ps.readUserState(user); + if (pus.suspended && packageName.equals(pus.suspendingPackage)) { + ps.setSuspended(false, null, null, null, user); + } + } + } + } + } + @GuardedBy("mPackages") private boolean canSuspendPackageForUserLocked(String packageName, int userId) { if (isPackageDeviceAdmin(packageName, userId)) { @@ -14096,6 +14158,11 @@ public class PackageManagerService extends IPackageManager.Stub return false; } + if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + Slog.w(TAG, "Cannot suspend package: " + packageName); + return false; + } + return true; } @@ -18612,6 +18679,7 @@ public class PackageManagerService extends IPackageManager.Stub } final int removedUserId = (user != null) ? user.getIdentifier() : UserHandle.USER_ALL; + if (!clearPackageStateForUserLIF(ps, removedUserId, outInfo)) { return false; } @@ -18620,6 +18688,11 @@ public class PackageManagerService extends IPackageManager.Stub return true; } } + if (ps.getPermissionsState().hasPermission( + Manifest.permission.SUSPEND_APPS, user.getIdentifier())) { + onSuspendingPackageRemoved(packageName, user.getIdentifier()); + } + if (((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null && user.getIdentifier() != UserHandle.USER_ALL)) { @@ -18758,6 +18831,9 @@ public class PackageManagerService extends IPackageManager.Stub true /*notLaunched*/, false /*hidden*/, false /*suspended*/, + null, /*suspendingPackage*/ + null, /*suspendedAppExtras*/ + null, /*suspendedLauncherExtras*/ false /*instantApp*/, false /*virtualPreload*/, null /*lastDisableAppCaller*/, diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index d2ef67b74fef..28e32a54090a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -57,12 +57,14 @@ import android.content.pm.dex.DexMetadataHelper; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; +import android.os.BaseBundle; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.IUserManager; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -1503,27 +1505,55 @@ class PackageManagerShellCommand extends ShellCommand { private int runSuspend(boolean suspendedState) { final PrintWriter pw = getOutPrintWriter(); int userId = UserHandle.USER_SYSTEM; + final PersistableBundle appExtras = new PersistableBundle(); + final PersistableBundle launcherExtras = new PersistableBundle(); String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "--user": userId = UserHandle.parseUserArg(getNextArgRequired()); break; + case "--ael": + case "--aes": + case "--aed": + case "--lel": + case "--les": + case "--led": + final String key = getNextArgRequired(); + final String val = getNextArgRequired(); + if (!suspendedState) { + break; + } + final PersistableBundle bundleToInsert = + opt.startsWith("--a") ? appExtras : launcherExtras; + switch (opt.charAt(4)) { + case 'l': + bundleToInsert.putLong(key, Long.valueOf(val)); + break; + case 'd': + bundleToInsert.putDouble(key, Double.valueOf(val)); + break; + case 's': + bundleToInsert.putString(key, val); + break; + } + break; default: pw.println("Error: Unknown option: " + opt); return 1; } } - String packageName = getNextArg(); + final String packageName = getNextArg(); if (packageName == null) { pw.println("Error: package name not specified"); return 1; } - + final String callingPackage = + (Binder.getCallingUid() == Process.ROOT_UID) ? "root" : "com.android.shell"; try { mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState, - userId); + appExtras, launcherExtras, 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 a0ed12611e48..008a81cd9cb7 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -20,12 +20,16 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.Signature; +import android.os.BaseBundle; +import android.os.PersistableBundle; import android.service.pm.PackageProto; import android.util.ArraySet; import android.util.SparseArray; @@ -394,8 +398,13 @@ public abstract class PackageSettingBase extends SettingBase { return readUserState(userId).suspended; } - void setSuspended(boolean suspended, int userId) { - modifyUserState(userId).suspended = suspended; + void setSuspended(boolean suspended, String suspendingPackage, PersistableBundle appExtras, + PersistableBundle launcherExtras, int userId) { + final PackageUserState existingUserState = modifyUserState(userId); + existingUserState.suspended = suspended; + existingUserState.suspendingPackage = suspended ? suspendingPackage : null; + existingUserState.suspendedAppExtras = suspended ? appExtras : null; + existingUserState.suspendedLauncherExtras = suspended ? launcherExtras : null; } public boolean getInstantApp(int userId) { @@ -415,7 +424,9 @@ public abstract class PackageSettingBase extends SettingBase { } void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped, - boolean notLaunched, boolean hidden, boolean suspended, boolean instantApp, + boolean notLaunched, boolean hidden, boolean suspended, String suspendingPackage, + PersistableBundle suspendedAppExtras, PersistableBundle suspendedLauncherExtras, + boolean instantApp, boolean virtualPreload, String lastDisableAppCaller, ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, int domainVerifState, int linkGeneration, int installReason, @@ -428,6 +439,9 @@ public abstract class PackageSettingBase extends SettingBase { state.notLaunched = notLaunched; state.hidden = hidden; state.suspended = suspended; + state.suspendingPackage = suspendingPackage; + state.suspendedAppExtras = suspendedAppExtras; + state.suspendedLauncherExtras = suspendedLauncherExtras; state.lastDisableAppCaller = lastDisableAppCaller; state.enabledComponents = enabledComponents; state.disabledComponents = disabledComponents; @@ -594,6 +608,9 @@ public abstract class PackageSettingBase extends SettingBase { proto.write(PackageProto.UserInfoProto.INSTALL_TYPE, installType); proto.write(PackageProto.UserInfoProto.IS_HIDDEN, state.hidden); proto.write(PackageProto.UserInfoProto.IS_SUSPENDED, state.suspended); + if (state.suspended) { + proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE, state.suspendingPackage); + } proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.stopped); proto.write(PackageProto.UserInfoProto.IS_LAUNCHED, !state.notLaunched); proto.write(PackageProto.UserInfoProto.ENABLED_STATE, state.enabled); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a38cbda245ca..d0e854436970 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -31,6 +31,7 @@ import static android.os.Process.PACKAGE_INFO_GID; import static android.os.Process.SYSTEM_UID; import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.NonNull; import android.annotation.Nullable; @@ -58,6 +59,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.Message; import android.os.PatternMatcher; +import android.os.PersistableBundle; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; @@ -199,6 +201,8 @@ public final class Settings { private static final String TAG_DEFAULT_BROWSER = "default-browser"; private static final String TAG_DEFAULT_DIALER = "default-dialer"; private static final String TAG_VERSION = "version"; + private static final String TAG_SUSPENDED_APP_EXTRAS = "suspended-app-extras"; + private static final String TAG_SUSPENDED_LAUNCHER_EXTRAS = "suspended-launcher-extras"; public static final String ATTR_NAME = "name"; public static final String ATTR_PACKAGE = "package"; @@ -217,6 +221,7 @@ public final class Settings { // New name for the above attribute. private static final String ATTR_HIDDEN = "hidden"; private static final String ATTR_SUSPENDED = "suspended"; + private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package"; // Legacy, uninstall blocks are stored separately. @Deprecated private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall"; @@ -728,6 +733,9 @@ public final class Settings { true /*notLaunched*/, false /*hidden*/, false /*suspended*/, + null, /*suspendingPackage*/ + null, /*suspendedAppExtras*/ + null, /*suspendedLauncherExtras*/ instantApp, virtualPreload, null /*lastDisableAppCaller*/, @@ -1619,6 +1627,9 @@ public final class Settings { false /*notLaunched*/, false /*hidden*/, false /*suspended*/, + null, /*suspendingPackage*/ + null, /*suspendedAppExtras*/ + null, /*suspendedLauncherExtras*/ false /*instantApp*/, false /*virtualPreload*/, null /*lastDisableAppCaller*/, @@ -1691,6 +1702,12 @@ public final class Settings { final boolean suspended = XmlUtils.readBooleanAttribute(parser, ATTR_SUSPENDED, false); + String suspendingPackage = parser.getAttributeValue(null, + ATTR_SUSPENDING_PACKAGE); + if (suspended && suspendingPackage == null) { + suspendingPackage = PLATFORM_PACKAGE_NAME; + } + final boolean blockUninstall = XmlUtils.readBooleanAttribute(parser, ATTR_BLOCK_UNINSTALL, false); final boolean instantApp = XmlUtils.readBooleanAttribute(parser, @@ -1716,6 +1733,8 @@ public final class Settings { ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; + PersistableBundle suspendedAppExtras = null; + PersistableBundle suspendedLauncherExtras = null; int packageDepth = parser.getDepth(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -1725,11 +1744,22 @@ public final class Settings { || type == XmlPullParser.TEXT) { continue; } - tagName = parser.getName(); - if (tagName.equals(TAG_ENABLED_COMPONENTS)) { - enabledComponents = readComponentsLPr(parser); - } else if (tagName.equals(TAG_DISABLED_COMPONENTS)) { - disabledComponents = readComponentsLPr(parser); + switch (parser.getName()) { + case TAG_ENABLED_COMPONENTS: + enabledComponents = readComponentsLPr(parser); + break; + case TAG_DISABLED_COMPONENTS: + disabledComponents = readComponentsLPr(parser); + break; + case TAG_SUSPENDED_APP_EXTRAS: + suspendedAppExtras = PersistableBundle.restoreFromXml(parser); + break; + case TAG_SUSPENDED_LAUNCHER_EXTRAS: + suspendedLauncherExtras = PersistableBundle.restoreFromXml(parser); + break; + default: + Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag " + + TAG_PACKAGE); } } @@ -1737,7 +1767,8 @@ public final class Settings { setBlockUninstallLPw(userId, name, true); } ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched, - hidden, suspended, instantApp, virtualPreload, enabledCaller, + hidden, suspended, suspendingPackage, suspendedAppExtras, + suspendedLauncherExtras, instantApp, virtualPreload, enabledCaller, enabledComponents, disabledComponents, verifState, linkGeneration, installReason, harmfulAppWarning); } else if (tagName.equals("preferred-activities")) { @@ -2046,6 +2077,27 @@ public final class Settings { } if (ustate.suspended) { serializer.attribute(null, ATTR_SUSPENDED, "true"); + serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, ustate.suspendingPackage); + if (ustate.suspendedAppExtras != null) { + serializer.startTag(null, TAG_SUSPENDED_APP_EXTRAS); + try { + ustate.suspendedAppExtras.saveToXml(serializer); + } catch (XmlPullParserException xmle) { + Slog.wtf(TAG, "Exception while trying to write suspendedAppExtras for " + + pkg + ". Will be lost on reboot", xmle); + } + serializer.endTag(null, TAG_SUSPENDED_APP_EXTRAS); + } + if (ustate.suspendedLauncherExtras != null) { + serializer.startTag(null, TAG_SUSPENDED_LAUNCHER_EXTRAS); + try { + ustate.suspendedLauncherExtras.saveToXml(serializer); + } catch (XmlPullParserException xmle) { + Slog.wtf(TAG, "Exception while trying to write suspendedLauncherExtras" + + " for " + pkg + ". Will be lost on reboot", xmle); + } + serializer.endTag(null, TAG_SUSPENDED_LAUNCHER_EXTRAS); + } } if (ustate.instantApp) { serializer.attribute(null, ATTR_INSTANT_APP, "true"); @@ -4697,6 +4749,10 @@ public final class Settings { pw.print(ps.getHidden(user.id)); pw.print(" suspended="); pw.print(ps.getSuspended(user.id)); + if (ps.getSuspended(user.id)) { + pw.print(" suspendingPackage="); + pw.print(ps.readUserState(user.id).suspendingPackage); + } pw.print(" stopped="); pw.print(ps.getStopped(user.id)); pw.print(" notLaunched="); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 884f348050ec..2e07703020e8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9252,7 +9252,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long id = mInjector.binderClearCallingIdentity(); try { return mIPackageManager.setPackagesSuspendedAsUser( - packageNames, suspended, callingUserId); + packageNames, suspended, 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 0ca0a1a104c8..cdb339ae4b92 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -36,6 +36,7 @@ LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl \ aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl LOCAL_SRC_FILES += $(call all-java-files-under, test-apps/JobTestApp/src) +LOCAL_SRC_FILES += $(call all-java-files-under, test-apps/SuspendTestApp/src) LOCAL_JAVA_LIBRARIES := \ android.hidl.manager-V1.0-java \ diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 372b8ccbdd7b..ce98d658d329 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -62,6 +62,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> + <uses-permission android:name="android.permission.SUSPEND_APPS"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 23e5072654e7..082827c23b53 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -15,9 +15,11 @@ --> <configuration description="Runs Frameworks Services Tests."> <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="JobTestApp.apk" /> <option name="test-file-name" value="ConnTestApp.apk" /> + <option name="test-file-name" value="SuspendTestApp.apk" /> </target_preparer> <option name="test-suite-tag" value="apct" /> diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java index 10a21fdf6f3e..2d5afadbf9f8 100644 --- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java +++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java @@ -32,6 +32,7 @@ import android.content.res.XmlResourceParser; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.PersistableBundle; import android.os.UserHandle; import android.os.storage.VolumeInfo; @@ -874,8 +875,8 @@ public class PackageManagerStub extends PackageManager { } @Override - public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, - int userId) { + public String[] setPackagesSuspended(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage) { return new String[0]; } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 53d97e7957c8..ebb424806296 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -21,13 +21,12 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -39,12 +38,13 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.UserInfo; +import android.os.BaseBundle; +import android.os.PersistableBundle; import android.os.UserHandle; import android.os.UserManagerInternal; -import android.security.keystore.ArrayUtils; import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -54,8 +54,8 @@ import com.android.internal.os.AtomicFile; import com.android.server.LocalServices; import com.android.server.pm.permission.PermissionManagerInternal; import com.android.server.pm.permission.PermissionManagerService; -import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,9 +71,9 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class PackageManagerSettingsTests { - private static final String PACKAGE_NAME_2 = "com.google.app2"; + private static final String PACKAGE_NAME_2 = "com.android.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; - private static final String PACKAGE_NAME_1 = "com.google.app1"; + private static final String PACKAGE_NAME_1 = "com.android.app1"; public static final String TAG = "PackageManagerSettingsTests"; protected final String PREFIX = "android.content.pm"; @@ -156,6 +156,92 @@ public class PackageManagerSettingsTests { assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT)); } + private PersistableBundle getPersistableBundle(String packageName, long longVal, + double doubleVal, boolean boolVal, String textVal) { + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(packageName + ".TEXT_VALUE", textVal); + bundle.putLong(packageName + ".LONG_VALUE", longVal); + bundle.putBoolean(packageName + ".BOOL_VALUE", boolVal); + bundle.putDouble(packageName + ".DOUBLE_VALUE", doubleVal); + return bundle; + } + + @Test + public void testReadPackageRestrictions_oldSuspendInfo() { + writePackageRestrictions_oldSuspendInfoXml(0); + final Object lock = new Object(); + final Context context = InstrumentationRegistry.getTargetContext(); + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, lock); + settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); + settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2)); + settingsUnderTest.readPackageRestrictionsLPr(0); + + final PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1); + final PackageUserState packageUserState1 = ps1.readUserState(0); + assertThat(packageUserState1.suspended, is(true)); + assertThat("android".equals(packageUserState1.suspendingPackage), is(true)); + + final PackageSetting ps2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2); + final PackageUserState packageUserState2 = ps2.readUserState(0); + assertThat(packageUserState2.suspended, is(false)); + assertThat(packageUserState2.suspendingPackage, is(nullValue())); + } + + @Test + public void testReadWritePackageRestrictions_newSuspendInfo() { + final Context context = InstrumentationRegistry.getTargetContext(); + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, new Object()); + final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); + final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2); + final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3); + + final PersistableBundle appExtras1 = getPersistableBundle( + PACKAGE_NAME_1, 1L, 0.01, true, "appString1"); + final PersistableBundle launcherExtras1 = getPersistableBundle( + PACKAGE_NAME_1, 10L, 0.1, false, "launcherString1"); + ps1.setSuspended(true, "suspendingPackage1", appExtras1, launcherExtras1, 0); + settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1); + + ps2.setSuspended(true, "suspendingPackage2", null, null, 0); + settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); + + ps3.setSuspended(false, "irrelevant", null, null, 0); + settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3); + + settingsUnderTest.writePackageRestrictionsLPr(0); + + settingsUnderTest.mPackages.clear(); + settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); + settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2)); + settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3)); + // now read and verify + settingsUnderTest.readPackageRestrictionsLPr(0); + final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1). + readUserState(0); + assertThat(readPus1.suspended, is(true)); + assertThat(readPus1.suspendingPackage, equalTo("suspendingPackage1")); + assertThat(BaseBundle.kindofEquals(readPus1.suspendedAppExtras, appExtras1), is(true)); + assertThat(BaseBundle.kindofEquals(readPus1.suspendedLauncherExtras, launcherExtras1), + is(true)); + + final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2). + readUserState(0); + assertThat(readPus2.suspended, is(true)); + assertThat(readPus2.suspendingPackage, equalTo("suspendingPackage2")); + assertThat(readPus2.suspendedAppExtras, is(nullValue())); + assertThat(readPus2.suspendedLauncherExtras, is(nullValue())); + + final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3). + readUserState(0); + assertThat(readPus3.suspended, is(false)); + } + + @Test + public void testPackageRestrictionsSuspendedDefault() { + final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1); + assertThat(defaultSetting.getSuspended(0), is(false)); + } + @Test public void testEnableDisable() { // Write the package files and make sure they're parsed properly the first time @@ -686,6 +772,26 @@ public class PackageManagerSettingsTests { null /*usesStaticLibrariesVersions*/); } + private PackageSetting createPackageSetting(String packageName) { + return new PackageSetting( + packageName, + packageName, + INITIAL_CODE_PATH /*codePath*/, + INITIAL_CODE_PATH /*resourcePath*/, + null /*legacyNativeLibraryPathString*/, + "x86_64" /*primaryCpuAbiString*/, + "x86" /*secondaryCpuAbiString*/, + null /*cpuAbiOverrideString*/, + INITIAL_VERSION_CODE, + 0, + 0 /*privateFlags*/, + null /*parentPackageName*/, + null /*childPackageNames*/, + 0, + null /*usesStaticLibraries*/, + null /*usesStaticLibrariesVersions*/); + } + private @NonNull List<UserInfo> createFakeUsers() { ArrayList<UserInfo> users = new ArrayList<>(); users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED)); @@ -718,13 +824,13 @@ public class PackageManagerSettingsTests { + "<item name=\"android.permission.ACCESS_WIMAX_STATE\" package=\"android\" />" + "<item name=\"android.permission.REBOOT\" package=\"android\" protection=\"18\" />" + "</permissions>" - + "<package name=\"com.google.app1\" codePath=\"/system/app/app1.apk\" nativeLibraryPath=\"/data/data/com.google.app1/lib\" flags=\"1\" ft=\"1360e2caa70\" it=\"135f2f80d08\" ut=\"1360e2caa70\" version=\"1109\" sharedUserId=\"11000\">" + + "<package name=\"com.android.app1\" codePath=\"/system/app/app1.apk\" nativeLibraryPath=\"/data/data/com.android.app1/lib\" flags=\"1\" ft=\"1360e2caa70\" it=\"135f2f80d08\" ut=\"1360e2caa70\" version=\"1109\" sharedUserId=\"11000\">" + "<sigs count=\"1\">" + "<cert index=\"0\" key=\"" + KeySetStrings.ctsKeySetCertA + "\" />" + "</sigs>" + "<proper-signing-keyset identifier=\"1\" />" + "</package>" - + "<package name=\"com.google.app2\" codePath=\"/system/app/app2.apk\" nativeLibraryPath=\"/data/data/com.google.app2/lib\" flags=\"1\" ft=\"1360e578718\" it=\"135f2f80d08\" ut=\"1360e578718\" version=\"15\" enabled=\"3\" userId=\"11001\">" + + "<package name=\"com.android.app2\" codePath=\"/system/app/app2.apk\" nativeLibraryPath=\"/data/data/com.android.app2/lib\" flags=\"1\" ft=\"1360e578718\" it=\"135f2f80d08\" ut=\"1360e578718\" version=\"15\" enabled=\"3\" userId=\"11001\">" + "<sigs count=\"1\">" + "<cert index=\"0\" />" + "</sigs>" @@ -774,11 +880,26 @@ public class PackageManagerSettingsTests { + "</packages>").getBytes()); } + private void writePackageRestrictions_oldSuspendInfoXml(final int userId) { + writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/" + + userId + "/package-restrictions.xml"), + ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<package-restrictions>\n" + + " <pkg name=\"" + PACKAGE_NAME_1 + "\" suspended=\"true\" />" + + " <pkg name=\"" + PACKAGE_NAME_2 + "\" suspended=\"false\" />" + + " <preferred-activities />\n" + + " <persistent-preferred-activities />\n" + + " <crossProfile-intent-filters />\n" + + " <default-apps />\n" + + "</package-restrictions>\n") + .getBytes()); + } + private void writeStoppedPackagesXml() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"), ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" + "<stopped-packages>" - + "<pkg name=\"com.google.app1\" nl=\"1\" />" + + "<pkg name=\"com.android.app1\" nl=\"1\" />" + "<pkg name=\"com.android.app3\" nl=\"1\" />" + "</stopped-packages>") .getBytes()); @@ -786,8 +907,8 @@ public class PackageManagerSettingsTests { private void writePackagesList() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"), - ( "com.google.app1 11000 0 /data/data/com.google.app1 seinfo1" - + "com.google.app2 11001 0 /data/data/com.google.app2 seinfo2" + ( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1" + + "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2" + "com.android.app3 11030 0 /data/data/com.android.app3 seinfo3") .getBytes()); } @@ -828,6 +949,11 @@ public class PackageManagerSettingsTests { }); } + @After + public void tearDown() throws Exception { + deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir()); + } + private void verifyKeySetMetaData(Settings settings) throws ReflectiveOperationException, IllegalAccessException { ArrayMap<String, PackageSetting> packages = settings.mPackages; @@ -871,9 +997,9 @@ public class PackageManagerSettingsTests { assertThat(KeySetUtils.getLastIssuedKeySetId(ksms), is(4L)); /* verify packages have been given the appropriate information */ - PackageSetting ps = packages.get("com.google.app1"); + PackageSetting ps = packages.get("com.android.app1"); assertThat(ps.keySetData.getProperSigningKeySet(), is(1L)); - ps = packages.get("com.google.app2"); + ps = packages.get("com.android.app2"); assertThat(ps.keySetData.getProperSigningKeySet(), is(1L)); assertThat(ps.keySetData.getAliases().get("AB"), is(4L)); ps = packages.get("com.android.app3"); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java index 50be8dbd16a8..4e1418c7a6e4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java @@ -23,8 +23,9 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import android.content.pm.PackageUserState; +import android.os.PersistableBundle; +import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; import org.junit.Test; @@ -170,4 +171,44 @@ public class PackageUserStateTest { testUserState03.enabledComponents.add("com.android.unit_test_04"); assertThat(testUserState03.equals(oldUserState), is(false)); } + + @Test + public void testPackageUserState05() { + PersistableBundle appExtras1 = new PersistableBundle(); + PersistableBundle appExtras2 = new PersistableBundle(); + appExtras1.putInt("appExtraId", 1); + appExtras2.putInt("appExtraId", 2); + PersistableBundle launcherExtras1 = new PersistableBundle(); + PersistableBundle launcherExtras2 = new PersistableBundle(); + launcherExtras1.putString("name", "launcherExtras1"); + launcherExtras2.putString("name", "launcherExtras2"); + final String suspendingPackage1 = "package1"; + final String suspendingPackage2 = "package2"; + + final PackageUserState testUserState1 = new PackageUserState(); + testUserState1.suspended = true; + testUserState1.suspendedAppExtras = appExtras1; + testUserState1.suspendedLauncherExtras = launcherExtras1; + testUserState1.suspendingPackage = suspendingPackage1; + + final PackageUserState testUserState2 = new PackageUserState(testUserState1); + assertThat(testUserState1.equals(testUserState2), is(true)); + testUserState2.suspendingPackage = suspendingPackage2; + assertThat(testUserState1.equals(testUserState2), is(false)); + + testUserState2.suspendingPackage = testUserState1.suspendingPackage; + testUserState2.suspendedAppExtras = appExtras2; + assertThat(testUserState1.equals(testUserState2), is(false)); + + testUserState2.suspendedAppExtras = testUserState1.suspendedAppExtras; + testUserState2.suspendedLauncherExtras = launcherExtras2; + assertThat(testUserState1.equals(testUserState2), is(false)); + + // Everything is different but irrelevant if suspended is false + testUserState2.suspended = testUserState1.suspended = false; + testUserState2.suspendedAppExtras = appExtras2; + testUserState2.suspendingPackage = suspendingPackage2; + assertThat(testUserState1.equals(testUserState2), is(true)); + } + } diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java new file mode 100644 index 000000000000..d702318761d1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java @@ -0,0 +1,127 @@ +/* + * 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 org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.BaseBundle; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.PersistableBundle; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +@MediumTest +public class SuspendPackagesTest { + private static final String TEST_APP_PACKAGE_NAME = SuspendTestReceiver.PACKAGE_NAME; + private static final String[] PACKAGES_TO_SUSPEND = new String[]{TEST_APP_PACKAGE_NAME}; + + private Context mContext; + private PackageManager mPackageManager; + private Handler mReceiverHandler; + private ComponentName mTestReceiverComponent; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mPackageManager = mContext.getPackageManager(); + mPackageManager.setPackagesSuspended(PACKAGES_TO_SUSPEND, false, null, null, null); + mReceiverHandler = new Handler(Looper.getMainLooper()); + mTestReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME, + SuspendTestReceiver.class.getCanonicalName()); + } + + private Bundle requestAppAction(String action) throws InterruptedException { + final AtomicReference<Bundle> result = new AtomicReference<>(); + final CountDownLatch receiverLatch = new CountDownLatch(1); + + final Intent broadcastIntent = new Intent(action) + .setComponent(mTestReceiverComponent) + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendOrderedBroadcast(broadcastIntent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + result.set(getResultExtras(true)); + receiverLatch.countDown(); + } + }, mReceiverHandler, 0, null, null); + + assertTrue("Test receiver timed out ", receiverLatch.await(5, TimeUnit.SECONDS)); + return result.get(); + } + + private PersistableBundle getExtras(String keyPrefix, long lval, String sval, double dval) { + final PersistableBundle extras = new PersistableBundle(3); + extras.putLong(keyPrefix + ".LONG_VALUE", lval); + extras.putDouble(keyPrefix + ".DOUBLE_VALUE", dval); + extras.putString(keyPrefix + ".STRING_VALUE", sval); + return extras; + } + + private void suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras) { + final String[] unchangedPackages = mPackageManager.setPackagesSuspended( + PACKAGES_TO_SUSPEND, true, appExtras, launcherExtras, null); + assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0); + } + + @Test + public void testIsPackageSuspended() { + suspendTestPackage(null, null); + assertTrue("isPackageSuspended is false", + mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME)); + } + + @Test + public void testSuspendedStateFromApp() throws Exception { + Bundle resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE); + assertFalse(resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED, true)); + assertNull(resultFromApp.getParcelable(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS)); + + final PersistableBundle appExtras = getExtras("appExtras", 20, "20", 0.2); + suspendTestPackage(appExtras, null); + + resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE); + assertTrue("resultFromApp:suspended is false", + resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED)); + final PersistableBundle receivedAppExtras = + resultFromApp.getParcelable(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS); + receivedAppExtras.get(""); // hack to unparcel the bundles + appExtras.get(""); + assertTrue("Received app extras " + receivedAppExtras + " different to the ones supplied", + BaseBundle.kindofEquals(appExtras, receivedAppExtras)); + } +} diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk b/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk new file mode 100644 index 000000000000..40a34b945f44 --- /dev/null +++ b/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk @@ -0,0 +1,30 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := current + +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := SuspendTestApp +LOCAL_DEX_PREOPT := false +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..70a1fd0e6430 --- /dev/null +++ b/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.suspendtestapp"> + + <application> + <activity android:name=".SuspendTestActivity" + android:exported="true" /> + <receiver android:name=".SuspendTestReceiver" + android:exported="true" /> + </application> + +</manifest>
\ No newline at end of file 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 new file mode 100644 index 000000000000..fa5fc5863a0e --- /dev/null +++ b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestActivity.java @@ -0,0 +1,24 @@ +/* + * 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.servicestests.apps.suspendtestapp; + +import android.app.Activity; + +public class SuspendTestActivity extends Activity { + private static final String TAG = SuspendTestActivity.class.getSimpleName(); + +}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java new file mode 100644 index 000000000000..6f353a008bea --- /dev/null +++ b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java @@ -0,0 +1,56 @@ +/* + * 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.servicestests.apps.suspendtestapp; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.util.Log; + +public class SuspendTestReceiver extends BroadcastReceiver { + private static final String TAG = SuspendTestReceiver.class.getSimpleName(); + + public static final String PACKAGE_NAME = "com.android.servicestests.apps.suspendtestapp"; + public static final String ACTION_GET_SUSPENDED_STATE = + PACKAGE_NAME + ".action.GET_SUSPENDED_STATE"; + public static final String EXTRA_SUSPENDED = PACKAGE_NAME + ".extra.SUSPENDED"; + public static final String EXTRA_SUSPENDED_APP_EXTRAS = + PACKAGE_NAME + ".extra.SUSPENDED_APP_EXTRAS"; + + private PackageManager mPm; + + @Override + public void onReceive(Context context, Intent intent) { + mPm = context.getPackageManager(); + Log.d(TAG, "Received request action " + intent.getAction()); + switch (intent.getAction()) { + case ACTION_GET_SUSPENDED_STATE: + final Bundle result = new Bundle(); + final boolean suspended = mPm.isPackageSuspended(); + final PersistableBundle appExtras = mPm.getSuspendedPackageAppExtras(); + result.putBoolean(EXTRA_SUSPENDED, suspended); + result.putParcelable(EXTRA_SUSPENDED_APP_EXTRAS, appExtras); + setResult(0, null, result); + break; + default: + Log.e(TAG, "Unknown action: " + intent.getAction()); + } + } +} diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java index 8ebb77d7a350..c2aca6b4c185 100644 --- a/test-mock/src/android/test/mock/MockPackageManager.java +++ b/test-mock/src/android/test/mock/MockPackageManager.java @@ -54,6 +54,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; +import android.os.PersistableBundle; import android.os.UserHandle; import android.os.storage.VolumeInfo; @@ -951,7 +952,8 @@ public class MockPackageManager extends PackageManager { /** @hide */ @Override - public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean hidden, int userId) { + public String[] setPackagesSuspended(String[] packageNames, boolean hidden, + PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage) { throw new UnsupportedOperationException(); } |