summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Suprabh Shukla <suprabh@google.com> 2018-03-08 18:21:50 -0800
committer Suprabh Shukla <suprabh@google.com> 2018-03-22 12:59:57 -0700
commit021b57ab8df0927aa1f78a2f3bb01d5e70594b1a (patch)
treef051df742120d34fc3ef87ee4c1d65b2e970d6ab
parent5d9617c439d0f85b67ba0d21e43f665ab9bf13ae (diff)
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
-rw-r--r--api/current.txt2
-rw-r--r--api/system-current.txt5
-rw-r--r--core/java/android/app/ApplicationPackageManager.java44
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl12
-rw-r--r--core/java/android/content/pm/PackageManager.java120
-rw-r--r--core/java/android/content/pm/PackageUserState.java22
-rw-r--r--core/java/android/os/BaseBundle.java17
-rw-r--r--core/proto/android/service/package.proto1
-rw-r--r--core/res/AndroidManifest.xml7
-rw-r--r--services/core/java/com/android/server/am/RecentTasks.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java122
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java36
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java23
-rw-r--r--services/core/java/com/android/server/pm/Settings.java68
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2
-rw-r--r--services/tests/servicestests/Android.mk1
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/AndroidTest.xml2
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java154
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java127
-rw-r--r--services/tests/servicestests/test-apps/SuspendTestApp/Android.mk30
-rw-r--r--services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml27
-rw-r--r--services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestActivity.java24
-rw-r--r--services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java56
-rw-r--r--test-mock/src/android/test/mock/MockPackageManager.java4
27 files changed, 891 insertions, 68 deletions
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<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);
@@ -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<android.content.pm.ResolveInfo> 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<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 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.
*
- * <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
@@ -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.
+ *
+ * <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 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" />
+ <!-- @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 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<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)) {
@@ -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<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 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 @@
<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 613d0afea183..8298ff3ab41a 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;
@@ -869,8 +870,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 1af7c3a479b6..108751a36a08 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -53,6 +53,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;
@@ -945,7 +946,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();
}