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);
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);
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";
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);
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;
}
@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);
/** @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,
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;
/**
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.
*/
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;
/**
* 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
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>
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;
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;
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;
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;
}
}
/**
+ * 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) {
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".
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
-->
&& 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);
}
}
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;
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;
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;
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;
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;
@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)) {
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)) {
}
}
+ 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)) {
return false;
}
+ if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
+ Slog.w(TAG, "Cannot suspend package: " + packageName);
+ return false;
+ }
+
return true;
}
}
final int removedUserId = (user != null) ? user.getIdentifier()
: UserHandle.USER_ALL;
+
if (!clearPackageStateForUserLIF(ps, removedUserId, outInfo)) {
return false;
}
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)) {
true /*notLaunched*/,
false /*hidden*/,
false /*suspended*/,
+ null, /*suspendingPackage*/
+ null, /*suspendedAppExtras*/
+ null, /*suspendedLauncherExtras*/
false /*instantApp*/,
false /*virtualPreload*/,
null /*lastDisableAppCaller*/,
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;
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;
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;
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) {
}
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,
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;
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);
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;
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;
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";
// 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";
true /*notLaunched*/,
false /*hidden*/,
false /*suspended*/,
+ null, /*suspendingPackage*/
+ null, /*suspendedAppExtras*/
+ null, /*suspendedLauncherExtras*/
instantApp,
virtualPreload,
null /*lastDisableAppCaller*/,
false /*notLaunched*/,
false /*hidden*/,
false /*suspended*/,
+ null, /*suspendingPackage*/
+ null, /*suspendedAppExtras*/
+ null, /*suspendedLauncherExtras*/
false /*instantApp*/,
false /*virtualPreload*/,
null /*lastDisableAppCaller*/,
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,
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
|| 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);
}
}
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")) {
}
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");
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=");
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);
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 \
<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"
-->
<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" />
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;
}
@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];
}
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;
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;
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;
@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";
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
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));
+ "<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>"
+ "</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());
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());
}
});
}
+ @After
+ public void tearDown() throws Exception {
+ deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
+ }
+
private void verifyKeySetMetaData(Settings settings)
throws ReflectiveOperationException, IllegalAccessException {
ArrayMap<String, PackageSetting> packages = settings.mPackages;
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");
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;
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));
+ }
+
}
--- /dev/null
+/*
+ * 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));
+ }
+}
--- /dev/null
+# 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
--- /dev/null
+<?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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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());
+ }
+ }
+}
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;
/** @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();
}