From: Suprabh Shukla Date: Fri, 9 Mar 2018 02:21:50 +0000 (-0800) Subject: APIs to suspend packages with SUSPEND_APPS permission X-Git-Tag: android-x86-9.0-r1~163^2~130^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=021b57ab8df0927aa1f78a2f3bb01d5e70594b1a;p=android-x86%2Fframeworks-base.git APIs to suspend packages with SUSPEND_APPS permission Changed the existing hidden api setPackagesSuspendedAsUser to a system api setPackagesSuspended that can be called by apps with either MANAGE_USERS or SUSPEND_APPS permission. Additionally, the suspending app can now specify optional extra information meant to be used by the suspended apps and the launcher to deal with this state. The following other APIs are added: - isPackageSuspended(): Apps can query whether they are in a suspended state - @SystemApi getPackageSuspendedAppExtras(String): Apps with permission SUSPEND_APPS can get the appExtras passed to PM when suspending the app. - @SystemApi setPackageSuspendedAppExtras(String, PersistableBundle): Apps with permission SUSPEND_APPS can update app extras for a suspended package. - getPackageSuspendedAppExtras(): Apps can call to get the appExtras passed in to PM when they were suspended. Test: Can be run via: atest com.android.server.pm.PackageManagerSettingsTests atest com.android.server.pm.PackageUserStateTest atest com.android.server.pm.SuspendPackagesTest Bug: 74336673 Change-Id: I3b9ed2c8478b34ee2e8986f5f5fddb2839d102e3 --- diff --git a/api/current.txt b/api/current.txt index 30b413a7245e..fba82a40ae24 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11160,6 +11160,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 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); @@ -11173,6 +11174,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 queryBroadcastReceivers(android.content.Intent, int); diff --git a/api/system-current.txt b/api/system-current.txt index af783caf7c12..19c6db6e9111 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -179,6 +179,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"; @@ -1007,15 +1008,19 @@ package android.content.pm { method public abstract java.util.List 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 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 21fb18a212a8..12fab2ade0c6 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -69,6 +69,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; @@ -2144,16 +2145,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); @@ -2164,6 +2191,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 bd7961fffca8..0fbc87e52b22 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; @@ -5504,28 +5505,49 @@ public abstract class PackageManager { /** * Puts the package in a suspended state, where attempts at starting activities are denied. * - *

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. + *

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.

* *

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.

+ * + *

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.

+ * + *

The caller must hold {@link Manifest.permission#SUSPEND_APPS} or + * {@link Manifest.permission#MANAGE_USERS} to use this api.

* * @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 @@ -5535,6 +5557,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. + * + *

The caller must hold permission {@link Manifest.permission#SUSPEND_APPS} to use this + * api.

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

Note: This just returns whatever {@link PersistableBundle} was passed to the system via + * {@code setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, + * String)} when suspending the package, which might be {@code null}.

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

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 9b11a33593bd..97ecd3f27b92 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1441,6 +1441,13 @@ android:label="@string/permlab_bluetooth" android:protectionLevel="normal" /> + + + diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index 3f05ecee77ef..a96a66d2cdf7 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 108247844c64..376cbec4433d 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; @@ -213,6 +211,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; @@ -237,7 +236,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; @@ -406,7 +404,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; @@ -13944,29 +13942,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 changedPackages = new ArrayList<>(packageNames.length); + final List changedPackages = new ArrayList<>(packageNames.length); // List of package names for whom the suspended state is not set as requested in this // method. - List unactionedPackages = new ArrayList<>(packageNames.length); - long callingId = Binder.clearCallingIdentity(); + final List 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)) { @@ -13975,42 +13989,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)) { @@ -14020,6 +14067,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)) { @@ -14076,6 +14138,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; } @@ -18592,6 +18659,7 @@ public class PackageManagerService extends IPackageManager.Stub } final int removedUserId = (user != null) ? user.getIdentifier() : UserHandle.USER_ALL; + if (!clearPackageStateForUserLIF(ps, removedUserId, outInfo)) { return false; } @@ -18600,6 +18668,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)) { @@ -18738,6 +18811,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 enabledComponents, ArraySet 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 enabledComponents = null; ArraySet 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 ab8a6c4d754a..2587a70aa275 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9243,7 +9243,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 @@ + +