Merge "Allow cross user package suspension" into main
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 467cd49..751368f 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import static android.app.admin.flags.Flags.crossUserSuspensionEnabled;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS;
@@ -59,6 +60,7 @@
public static final String EXTRA_SUSPENDED_PACKAGE = PACKAGE_NAME + ".extra.SUSPENDED_PACKAGE";
public static final String EXTRA_SUSPENDING_PACKAGE =
PACKAGE_NAME + ".extra.SUSPENDING_PACKAGE";
+ public static final String EXTRA_SUSPENDING_USER = PACKAGE_NAME + ".extra.SUSPENDING_USER";
public static final String EXTRA_DIALOG_INFO = PACKAGE_NAME + ".extra.DIALOG_INFO";
public static final String EXTRA_ACTIVITY_OPTIONS = PACKAGE_NAME + ".extra.ACTIVITY_OPTIONS";
public static final String EXTRA_UNSUSPEND_INTENT = PACKAGE_NAME + ".extra.UNSUSPEND_INTENT";
@@ -67,6 +69,7 @@
private IntentSender mOnUnsuspend;
private String mSuspendedPackage;
private String mSuspendingPackage;
+ private int mSuspendingUserId;
private int mNeutralButtonAction;
private int mUserId;
private PackageManager mPm;
@@ -117,7 +120,7 @@
.setPackage(mSuspendingPackage);
final String requiredPermission = Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS;
final ResolveInfo resolvedInfo = mPm.resolveActivityAsUser(moreDetailsIntent,
- MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE, mUserId);
+ MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE, mSuspendingUserId);
if (resolvedInfo != null && resolvedInfo.activityInfo != null
&& requiredPermission.equals(resolvedInfo.activityInfo.permission)) {
moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, mSuspendedPackage)
@@ -231,12 +234,17 @@
}
mSuspendedPackage = intent.getStringExtra(EXTRA_SUSPENDED_PACKAGE);
mSuspendingPackage = intent.getStringExtra(EXTRA_SUSPENDING_PACKAGE);
+ if (crossUserSuspensionEnabled()) {
+ mSuspendingUserId = intent.getIntExtra(EXTRA_SUSPENDING_USER, mUserId);
+ } else {
+ mSuspendingUserId = mUserId;
+ }
mSuppliedDialogInfo = intent.getParcelableExtra(EXTRA_DIALOG_INFO, android.content.pm.SuspendDialogInfo.class);
mOnUnsuspend = intent.getParcelableExtra(EXTRA_UNSUSPEND_INTENT, android.content.IntentSender.class);
if (mSuppliedDialogInfo != null) {
try {
mSuspendingAppResources = createContextAsUser(
- UserHandle.of(mUserId), /* flags */ 0).getPackageManager()
+ UserHandle.of(mSuspendingUserId), /* flags */ 0).getPackageManager()
.getResourcesForApplication(mSuspendingPackage);
} catch (PackageManager.NameNotFoundException ne) {
Slog.e(TAG, "Could not find resources for " + mSuspendingPackage, ne);
@@ -299,7 +307,7 @@
case BUTTON_ACTION_MORE_DETAILS:
if (mMoreDetailsIntent != null) {
startActivityAsUser(mMoreDetailsIntent, mOptions,
- UserHandle.of(mUserId));
+ UserHandle.of(mSuspendingUserId));
} else {
Slog.wtf(TAG, "Neutral button should not have existed!");
}
@@ -324,7 +332,7 @@
.putExtra(Intent.EXTRA_PACKAGE_NAME, mSuspendedPackage)
.setPackage(mSuspendingPackage)
.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- sendBroadcastAsUser(reportUnsuspend, UserHandle.of(mUserId));
+ sendBroadcastAsUser(reportUnsuspend, UserHandle.of(mSuspendingUserId));
if (mOnUnsuspend != null) {
Bundle activityOptions =
@@ -365,6 +373,9 @@
.putExtra(Intent.EXTRA_USER_ID, userId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ if (crossUserSuspensionEnabled()) {
+ intent.putExtra(EXTRA_SUSPENDING_USER, suspendingPackage.userId);
+ }
return intent;
}
}
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 068f4dd..d30f195 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -142,6 +142,7 @@
// UTC timestamp of first install for the user
optional int32 first_install_time_ms = 11;
optional ArchiveState archive_state = 12;
+ repeated int32 suspending_user = 13;
}
message InstallSourceProto {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 588c629..fd16221 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -542,7 +542,8 @@
final Computer snapshot = mPm.snapshotComputer();
for (final int affectedUserId : outInfo.mRemovedUsers) {
if (hadSuspendAppsPermission.get(affectedUserId)) {
- mPm.unsuspendForSuspendingPackage(snapshot, packageName, affectedUserId);
+ mPm.unsuspendForSuspendingPackage(snapshot, packageName,
+ affectedUserId /*suspendingUserId*/, true /*inAllUsers*/);
mPm.removeAllDistractingPackageRestrictions(snapshot, affectedUserId);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 8da1683..7a72e70 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.admin.flags.Flags.crossUserSuspensionEnabled;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.RESTRICTION_NONE;
@@ -45,6 +46,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
+import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -687,14 +689,17 @@
@Override
@Deprecated
public final void unsuspendAdminSuspendedPackages(int affectedUser) {
- final int suspendingUserId = affectedUser;
- mService.unsuspendForSuspendingPackage(snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId);
+ final int suspendingUserId =
+ crossUserSuspensionEnabled() ? UserHandle.USER_SYSTEM : affectedUser;
+ mService.unsuspendForSuspendingPackage(
+ snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId, /* inAllUsers= */ false);
}
@Override
@Deprecated
public final boolean isAdminSuspendingAnyPackages(int userId) {
- final int suspendingUserId = userId;
+ final int suspendingUserId =
+ crossUserSuspensionEnabled() ? UserHandle.USER_SYSTEM : userId;
return snapshot().isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, suspendingUserId, userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d215822..9a2b98f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.admin.flags.Flags.crossUserSuspensionEnabled;
import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -3181,27 +3182,53 @@
callingMethod);
}
- final int packageUid = snapshot.getPackageUid(suspender.packageName, 0, targetUserId);
- final boolean allowedPackageUid = packageUid == callingUid;
- // TODO(b/139383163): remove special casing for shell and enforce INTERACT_ACROSS_USERS_FULL
- final boolean allowedShell = callingUid == SHELL_UID
- && UserHandle.isSameApp(packageUid, callingUid);
+ if (crossUserSuspensionEnabled()) {
+ final int suspendingPackageUid =
+ snapshot.getPackageUid(suspender.packageName, 0, suspender.userId);
+ if (suspendingPackageUid != callingUid) {
+ throw new SecurityException("Suspender package %s doesn't match calling uid %d"
+ .formatted(suspender.packageName, callingUid));
+ }
+ if (targetUserId != suspender.userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingMethod);
+ }
+ } else {
+ // Here only SHELL can suspend across users
+ final int packageUid =
+ snapshot.getPackageUid(suspender.packageName, 0, targetUserId);
+ final boolean allowedPackageUid = packageUid == callingUid;
+ final boolean allowedShell = callingUid == SHELL_UID
+ && UserHandle.isSameApp(packageUid, callingUid);
- if (!allowedShell && !allowedPackageUid) {
- throw new SecurityException("Suspending package " + suspender.packageName
- + " in user " + targetUserId + " does not belong to calling uid " + callingUid);
+ if (!allowedShell && !allowedPackageUid) {
+ throw new SecurityException("Suspending package " + suspender.packageName
+ + " in user " + targetUserId + " does not belong to calling uid "
+ + callingUid);
+ }
}
}
+ /**
+ * @param inAllUsers Whether to unsuspend packages suspended by the given package in other
+ * users. This flag is only used when cross-user suspension is enabled.
+ */
void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage,
- @UserIdInt int suspendingUserId) {
+ @UserIdInt int suspendingUserId, boolean inAllUsers) {
// TODO: This can be replaced by a special parameter to iterate all packages, rather than
// this weird pre-collect of all packages.
final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]);
final Predicate<UserPackage> suspenderPredicate =
UserPackage.of(suspendingUserId, suspendingPackage)::equals;
- mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
- allPackages, suspenderPredicate, suspendingUserId);
+ if (!crossUserSuspensionEnabled() || !inAllUsers) {
+ mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
+ allPackages, suspenderPredicate, suspendingUserId);
+ } else {
+ for (int targetUserId: mUserManager.getUserIds()) {
+ mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
+ computer, allPackages, suspenderPredicate, targetUserId);
+ }
+ }
}
void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) {
@@ -4053,7 +4080,7 @@
// This app should not generally be allowed to get disabled by the UI, but
// if it ever does, we don't want to end up with some of the user's apps
// permanently suspended.
- unsuspendForSuspendingPackage(computer, packageName, userId);
+ unsuspendForSuspendingPackage(computer, packageName, userId, true /* inAllUsers */);
removeAllDistractingPackageRestrictions(computer, userId);
}
success = true;
@@ -4339,6 +4366,19 @@
}
mInstantAppRegistry.onUserRemoved(userId);
mPackageMonitorCallbackHelper.onUserRemoved(userId);
+ if (crossUserSuspensionEnabled()) {
+ cleanUpCrossUserSuspension(userId);
+ }
+ }
+
+ private void cleanUpCrossUserSuspension(int removedUser) {
+ final Computer computer = snapshotComputer();
+ var allPackages = computer.getAllAvailablePackageNames();
+ for (int targetUserId : mUserManager.getUserIds()) {
+ if (targetUserId == removedUser) continue;
+ mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages,
+ userPackage -> userPackage.userId == removedUser, targetUserId);
+ }
}
/**
@@ -4745,7 +4785,8 @@
if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
== PERMISSION_GRANTED) {
final Computer snapshot = snapshotComputer();
- unsuspendForSuspendingPackage(snapshot, packageName, userId);
+ unsuspendForSuspendingPackage(
+ snapshot, packageName, userId, true /* inAllUsers */);
removeAllDistractingPackageRestrictions(snapshot, userId);
synchronized (mLock) {
flushPackageRestrictionsAsUserInternalLocked(userId);
@@ -6239,7 +6280,9 @@
final boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
&& Flags.quarantinedEnabled();
final Computer snapshot = snapshotComputer();
- final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
+ final UserPackage suspender = crossUserSuspensionEnabled()
+ ? UserPackage.of(suspendingUserId, suspendingPackage)
+ : UserPackage.of(targetUserId, suspendingPackage);
enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
targetUserId, "setPackagesSuspendedAsUser");
return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
@@ -6707,7 +6750,10 @@
@Override
public String[] setPackagesSuspendedByAdmin(
@UserIdInt int userId, @NonNull String[] packageNames, boolean suspended) {
- final int suspendingUserId = userId;
+ // Suspension by admin isn't attributed to admin package but to the platform,
+ // Using USER_SYSTEM for consistency with other internal suspenders, like shell or root.
+ final int suspendingUserId =
+ crossUserSuspensionEnabled() ? UserHandle.USER_SYSTEM : userId;
final UserPackage suspender = UserPackage.of(
suspendingUserId, PackageManagerService.PLATFORM_PACKAGE_NAME);
return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 12eb88e..b44042c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.admin.flags.Flags.crossUserSuspensionEnabled;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
@@ -1240,6 +1241,10 @@
for (int j = 0; j < state.getSuspendParams().size(); j++) {
proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE,
state.getSuspendParams().keyAt(j).packageName);
+ if (crossUserSuspensionEnabled()) {
+ proto.write(PackageProto.UserInfoProto.SUSPENDING_USER,
+ state.getSuspendParams().keyAt(j).userId);
+ }
}
}
proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.isStopped());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e35a169..f5ed8d4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.admin.flags.Flags.crossUserSuspensionEnabled;
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;
@@ -342,6 +343,7 @@
private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags";
private static final String ATTR_SUSPENDED = "suspended";
private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
+ private static final String ATTR_SUSPENDING_USER = "suspending-user";
private static final String ATTR_OPTIONAL = "optional";
/**
@@ -2051,7 +2053,20 @@
Slog.wtf(TAG, "No suspendingPackage found inside tag " + TAG_SUSPEND_PARAMS);
return null;
}
- final int suspendingUserId = userId;
+ int suspendingUserId;
+ if (crossUserSuspensionEnabled()) {
+ suspendingUserId = parser.getAttributeInt(
+ null, ATTR_SUSPENDING_USER, UserHandle.USER_NULL);
+ if (suspendingUserId == UserHandle.USER_NULL) {
+ suspendingUserId = switch (suspendingPackage) {
+ case "root", "com.android.shell", PLATFORM_PACKAGE_NAME
+ -> UserHandle.USER_SYSTEM;
+ default -> userId;
+ };
+ }
+ } else {
+ suspendingUserId = userId;
+ }
return Map.entry(
UserPackage.of(suspendingUserId, suspendingPackage),
SuspendParams.restoreFromXml(parser));
@@ -2418,6 +2433,10 @@
serializer.startTag(null, TAG_SUSPEND_PARAMS);
serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
suspendingPackage.packageName);
+ if (crossUserSuspensionEnabled()) {
+ serializer.attributeInt(null, ATTR_SUSPENDING_USER,
+ suspendingPackage.userId);
+ }
final SuspendParams params =
ustate.getSuspendParams().valueAt(i);
if (params != null) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 6d3b8ac..4149e44 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -75,6 +75,7 @@
"compatibility-device-util-axt",
"flag-junit",
"am_flags_lib",
+ "device_policy_aconfig_flags_lib",
],
libs: [