Migrate permissions when leaving sharedUserId

- Retrieve the previous uid permission state and create a copy of it as
  the new app's uid state.
- Remove the app from the original shared user group. Other apps in the
  shared user group will perceive as if the original app is uninstalled.
- The new permission state is updated in updatePermissions() just like
  a normal package upgrade.

Test: atest CtsSharedUserMigrationTestCases
Bug: 179284822
Change-Id: I9d7c3b16959dd4b2c2684ddaf8bd223fb32c3c41
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c3bf03c..d92b65b4 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -667,6 +667,12 @@
      */
     public abstract @Nullable AndroidPackage getPackage(int uid);
 
+
+    /**
+     * Returns all packages for the given app ID.
+     */
+    public abstract @NonNull List<AndroidPackage> getPackagesForAppId(int appId);
+
     /**
      * Returns a list without a change observer.
      *
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index bf241d4..9412dda 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -50,6 +50,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Environment;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -575,6 +576,7 @@
                         Slog.w(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                     }
                     mPm.mPermissionManager.onPackageInstalled(pkg,
+                            Process.INVALID_UID /* previousAppId */,
                             PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
                             UserHandle.USER_ALL);
                     mPm.writeSettingsLPrTEMP();
@@ -907,6 +909,7 @@
             // The method below will take care of removing obsolete permissions and granting
             // install permissions.
             mPm.mPermissionManager.onPackageInstalled(pkg,
+                    Process.INVALID_UID /* previousAppId */,
                     PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
                     UserHandle.USER_ALL);
             for (final int userId : allUserHandles) {
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index bc7d95e..1df30f5 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -1622,7 +1622,7 @@
 
             AndroidPackage pkg = mPm.commitReconciledScanResultLocked(reconciledPkg,
                     request.mAllUsers);
-            updateSettingsLI(pkg, reconciledPkg.mInstallArgs, request.mAllUsers, res);
+            updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res);
 
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
             if (ps != null) {
@@ -1642,17 +1642,18 @@
         return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true);
     }
 
-    private void updateSettingsLI(AndroidPackage newPackage, InstallArgs installArgs,
+    private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg,
             int[] allUsers, PackageInstalledInfo res) {
-        updateSettingsInternalLI(newPackage, installArgs, allUsers, res);
+        updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, res);
     }
 
-    private void updateSettingsInternalLI(AndroidPackage pkg, InstallArgs installArgs,
+    private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg,
             int[] allUsers, PackageInstalledInfo res) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.getPackageName();
         final int[] installedForUsers = res.mOrigUsers;
+        final InstallArgs installArgs = reconciledPkg.mInstallArgs;
         final int installReason = installArgs.mInstallReason;
         InstallSource installSource = installArgs.mInstallSource;
         final String installerPackageName = installSource.installerPackageName;
@@ -1808,8 +1809,9 @@
                 }
                 final int autoRevokePermissionsMode = installArgs.mAutoRevokePermissionsMode;
                 permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
-                mPm.mPermissionManager.onPackageInstalled(pkg, permissionParamsBuilder.build(),
-                        userId);
+                final ScanResult scanResult = reconciledPkg.mScanResult;
+                mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId,
+                        permissionParamsBuilder.build(), userId);
             }
             res.mName = pkgName;
             res.mUid = pkg.getUid();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e62102c..853db55 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13102,6 +13102,7 @@
                                 pkgSetting.pkg.getRequestedPermissions());
                     }
                     mPermissionManager.onPackageInstalled(pkgSetting.pkg,
+                            Process.INVALID_UID /* previousAppId */,
                             permissionParamsBuilder.build(), userId);
                 }
 
@@ -20022,6 +20023,23 @@
             return PackageManagerService.this.getPackage(uid);
         }
 
+        @Override
+        public List<AndroidPackage> getPackagesForAppId(int appId) {
+            final Object obj;
+            synchronized (mLock) {
+                obj = mSettings.getSettingLPr(appId);
+            }
+            if (obj instanceof SharedUserSetting) {
+                final SharedUserSetting sus = (SharedUserSetting) obj;
+                return sus.getPackages();
+            } else if (obj instanceof PackageSetting) {
+                final PackageSetting ps = (PackageSetting) obj;
+                return List.of(ps.getPkg());
+            } else {
+                return Collections.emptyList();
+            }
+        }
+
         @Nullable
         @Override
         public PackageSetting getPackageSetting(String packageName) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index 5fab84e..a76e419 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -73,6 +73,7 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
+import android.os.Process;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -130,10 +131,12 @@
      */
     public boolean optimisticallyRegisterAppId(@NonNull ScanResult result)
             throws PackageManagerException {
-        if (!result.mExistingSettingCopied || result.mNeedsNewAppId) {
-            // THROWS: when we can't allocate a user id. add call to check if there's
-            // enough space to ensure we won't throw; otherwise, don't modify state
-            return mPm.mSettings.registerAppIdLPw(result.mPkgSetting, result.mNeedsNewAppId);
+        if (!result.mExistingSettingCopied || result.needsNewAppId()) {
+            synchronized (mPm.mLock) {
+                // THROWS: when we can't allocate a user id. add call to check if there's
+                // enough space to ensure we won't throw; otherwise, don't modify state
+                return mPm.mSettings.registerAppIdLPw(result.mPkgSetting, result.needsNewAppId());
+            }
         }
         return false;
     }
@@ -337,11 +340,11 @@
             }
         }
 
-        boolean leavingSharedUser = false;
+        int previousAppId = Process.INVALID_UID;
 
         if (pkgSetting != null && pkgSetting.sharedUser != sharedUserSetting) {
             if (pkgSetting.sharedUser != null && sharedUserSetting == null) {
-                leavingSharedUser = true;
+                previousAppId = pkgSetting.appId;
                 // Log that something is leaving shareduid and keep going
                 Slog.i(TAG,
                         "Package " + parsedPackage.getPackageName() + " shared user changed from "
@@ -630,7 +633,7 @@
 
         return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
                 !createNewPackage /* existingSettingCopied */,
-                leavingSharedUser /* needsNewAppId */, staticSharedLibraryInfo,
+                previousAppId, staticSharedLibraryInfo,
                 dynamicSharedLibraryInfos);
     }
 
diff --git a/services/core/java/com/android/server/pm/ScanResult.java b/services/core/java/com/android/server/pm/ScanResult.java
index 34f86ba..eb44a82 100644
--- a/services/core/java/com/android/server/pm/ScanResult.java
+++ b/services/core/java/com/android/server/pm/ScanResult.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.content.pm.SharedLibraryInfo;
+import android.os.Process;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -36,10 +37,11 @@
      */
     public final boolean mExistingSettingCopied;
     /**
-     * Whether or not the original PackageSetting needs to be updated with
-     * a new uid. Useful when leaving a sharedUserID.
+     * The previous app ID if the app decided to leave a shared user ID.
+     * The value is set *only* if the app is leaving a shared user ID.
+     * Default value is Process.INVALID_UID.
      */
-    public final boolean mNeedsNewAppId;
+    public final int mPreviousAppId;
     /**
      * The final package settings. This may be the same object passed in
      * the {@link ScanRequest}, but, with modified values.
@@ -57,7 +59,7 @@
             ScanRequest request, boolean success,
             @Nullable PackageSetting pkgSetting,
             @Nullable List<String> changedAbiCodePath, boolean existingSettingCopied,
-            boolean needsNewAppId,
+            int previousAppId,
             SharedLibraryInfo staticSharedLibraryInfo,
             List<SharedLibraryInfo> dynamicSharedLibraryInfos) {
         mRequest = request;
@@ -65,8 +67,16 @@
         mPkgSetting = pkgSetting;
         mChangedAbiCodePath = changedAbiCodePath;
         mExistingSettingCopied = existingSettingCopied;
-        mNeedsNewAppId = needsNewAppId;
+        mPreviousAppId = previousAppId;
         mStaticSharedLibraryInfo = staticSharedLibraryInfo;
         mDynamicSharedLibraryInfos = dynamicSharedLibraryInfos;
     }
+
+    /**
+     * Whether the original PackageSetting needs to be updated with
+     * a new app ID. Useful when leaving a sharedUserId.
+     */
+    public boolean needsNewAppId() {
+        return mPreviousAppId != Process.INVALID_UID;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index e40cb40..e503f21 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2620,8 +2620,7 @@
         // being upgraded to target a newer SDK, in which case dangerous permissions
         // are transformed from install time to runtime ones.
 
-        final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                pkg.getPackageName());
+        final PackageSetting ps = mPackageManagerInt.getPackageSetting(pkg.getPackageName());
         if (ps == null) {
             return;
         }
@@ -3954,21 +3953,24 @@
         }
     }
 
-    @UserIdInt
-    private int revokeSharedUserPermissionsForDeletedPackageInternal(
-            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
+    private void revokeSharedUserPermissionsForLeavingPackageInternal(
+            @Nullable AndroidPackage pkg, int appId, @NonNull List<AndroidPackage> sharedUserPkgs,
             @UserIdInt int userId) {
         if (pkg == null) {
             Slog.i(TAG, "Trying to update info for null package. Just ignoring");
-            return UserHandle.USER_NULL;
+            return;
         }
 
         // No shared user packages
         if (sharedUserPkgs.isEmpty()) {
-            return UserHandle.USER_NULL;
+            return;
         }
 
-        int affectedUserId = UserHandle.USER_NULL;
+        PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
+                pkg.getPackageName());
+        boolean isShadowingSystemPkg = disabledPs != null && disabledPs.appId == pkg.getUid();
+
+        boolean shouldKillUid = false;
         // Update permissions
         for (String eachPerm : pkg.getRequestedPermissions()) {
             // Check if another package in the shared user needs the permission.
@@ -3985,26 +3987,15 @@
                 continue;
             }
 
-            PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
-                    pkg.getPackageName());
-
-            // If the package is shadowing is a disabled system package,
+            // If the package is shadowing a disabled system package,
             // do not drop permissions that the shadowed package requests.
-            if (disabledPs != null) {
-                boolean reqByDisabledSysPkg = false;
-                for (String permission : disabledPs.pkg.getRequestedPermissions()) {
-                    if (permission.equals(eachPerm)) {
-                        reqByDisabledSysPkg = true;
-                        break;
-                    }
-                }
-                if (reqByDisabledSysPkg) {
-                    continue;
-                }
+            if (isShadowingSystemPkg
+                    && disabledPs.getPkg().getRequestedPermissions().contains(eachPerm)) {
+                continue;
             }
 
             synchronized (mLock) {
-                UidPermissionState uidState = getUidStateLocked(pkg, userId);
+                UidPermissionState uidState = getUidStateLocked(appId, userId);
                 if (uidState == null) {
                     Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
                             + " and user " + userId);
@@ -4019,12 +4010,18 @@
                 // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any
                 //  permission change?
                 if (uidState.removePermissionState(bp.getName()) && bp.hasGids()) {
-                    affectedUserId = userId;
+                    shouldKillUid = true;
                 }
             }
         }
 
-        return affectedUserId;
+        // If gids changed, kill all affected packages.
+        if (shouldKillUid) {
+            mHandler.post(() -> {
+                // This has to happen with no lock held.
+                killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
+            });
+        }
     }
 
     @GuardedBy("mLock")
@@ -4892,9 +4889,48 @@
         return true;
     }
 
-    private void onPackageInstalledInternal(@NonNull AndroidPackage pkg,
+    private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId,
             @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
             @UserIdInt int[] userIds) {
+        // If previousAppId is not Process.INVALID_UID, the package is performing a migration out
+        // of a shared user group. Operations we need to do before calling updatePermissions():
+        // - Retrieve the original uid permission state and create a copy of it as the new app's
+        //   uid state. The new permission state will be properly updated in updatePermissions().
+        // - Remove the app from the original shared user group. Other apps in the shared
+        //   user group will perceive as if the original app is uninstalled.
+        if (previousAppId != Process.INVALID_UID) {
+            final PackageSetting ps = mPackageManagerInt.getPackageSetting(pkg.getPackageName());
+            final List<AndroidPackage> origSharedUserPackages =
+                    mPackageManagerInt.getPackagesForAppId(previousAppId);
+
+            synchronized (mLock) {
+                // All users are affected
+                for (final int userId : getAllUserIds()) {
+                    // Retrieve the original uid state
+                    final UserPermissionState userState = mState.getUserState(userId);
+                    if (userState == null) {
+                        continue;
+                    }
+                    final UidPermissionState prevUidState = userState.getUidState(previousAppId);
+                    if (prevUidState == null) {
+                        continue;
+                    }
+
+                    // Insert new uid state by cloning the original one
+                    userState.createUidStateWithExisting(ps.getAppId(), prevUidState);
+
+                    // Remove original app ID from original shared user group
+                    // Should match the implementation of onPackageUninstalledInternal(...)
+                    if (origSharedUserPackages.isEmpty()) {
+                        removeUidStateAndResetPackageInstallPermissionsFixed(
+                                previousAppId, pkg.getPackageName(), userId);
+                    } else {
+                        revokeSharedUserPermissionsForLeavingPackageInternal(pkg, previousAppId,
+                                origSharedUserPackages, userId);
+                    }
+                }
+            }
+        }
         updatePermissions(pkg.getPackageName(), pkg);
         for (final int userId : userIds) {
             addAllowlistedRestrictedPermissionsInternal(pkg,
@@ -4931,6 +4967,9 @@
     private void onPackageUninstalledInternal(@NonNull String packageName, int appId,
             @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
             @UserIdInt int[] userIds) {
+        // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin
+        //  a shared UID permission state.
+
         // TODO: Move these checks to check PackageState to be more reliable.
         // System packages should always have an available APK.
         if (pkg != null && pkg.isSystem()
@@ -4956,16 +4995,8 @@
                 // or packages running under the shared user of the removed
                 // package if revoking the permissions requested only by the removed
                 // package is successful and this causes a change in gids.
-                final int userIdToKill = revokeSharedUserPermissionsForDeletedPackageInternal(pkg,
+                revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId,
                         sharedUserPkgs, userId);
-                final boolean shouldKill = userIdToKill != UserHandle.USER_NULL;
-                // If gids changed, kill all affected packages.
-                if (shouldKill) {
-                    mHandler.post(() -> {
-                        // This has to happen with no lock held.
-                        killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
-                    });
-                }
             }
         }
     }
@@ -5236,7 +5267,7 @@
         }
 
         @Override
-        public void onPackageInstalled(@NonNull AndroidPackage pkg,
+        public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
                 @NonNull PackageInstalledParams params, @UserIdInt int userId) {
             Objects.requireNonNull(pkg, "pkg");
             Objects.requireNonNull(params, "params");
@@ -5244,7 +5275,7 @@
                     || userId == UserHandle.USER_ALL, "userId");
             final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
                     : new int[] { userId };
-            onPackageInstalledInternal(pkg, params, userIds);
+            onPackageInstalledInternal(pkg, previousAppId, params, userIds);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index f4fb810..d2c4ec4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -252,11 +252,14 @@
      * Callback when a package has been installed for a user.
      *
      * @param pkg the installed package
+     * @param previousAppId the previous app ID if the package is leaving a shared UID,
+     *                      or Process.INVALID_UID
      * @param params the parameters passed in for package installation
      * @param userId the user ID this package is installed for
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    void onPackageInstalled(@NonNull AndroidPackage pkg, @NonNull PackageInstalledParams params,
+    void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
+            @NonNull PackageInstalledParams params,
             @UserIdInt int userId);
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/UserPermissionState.java b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
index 2c741cf..f39086b3 100644
--- a/services/core/java/com/android/server/pm/permission/UserPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
@@ -69,6 +69,15 @@
         return uidState;
     }
 
+    @NonNull
+    UidPermissionState createUidStateWithExisting(
+            @AppIdInt int appId, @NonNull UidPermissionState other) {
+        checkAppId(appId);
+        UidPermissionState uidState = new UidPermissionState(other);
+        mUidStates.put(appId, uidState);
+        return uidState;
+    }
+
     public void removeUidState(@AppIdInt int appId) {
         checkAppId(appId);
         mUidStates.delete(appId);