Clean up apks installed for a removed user

When a user is removed, enumerate through all installed packages
to see if any of them are not installed for any user. Delete the
package if no user has it "installed".

Added a pm option to install an apk for a specific user.

Fixed a crash in UserManagerService when executing the above
cleanup - dying users generate a null UserInfo.

Bug: 15426024
Change-Id: I571decde1ae1c257d0da6db153b896aad6d6bcb4
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2cb9077..fbaeb37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7698,8 +7698,21 @@
     public void installPackage(String originPath, IPackageInstallObserver2 observer,
             int installFlags, String installerPackageName, VerificationParams verificationParams,
             String packageAbiOverride) {
+        installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams,
+                packageAbiOverride, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
+            int installFlags, String installerPackageName, VerificationParams verificationParams,
+            String packageAbiOverride, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                 null);
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "installPackage " + userId);
+        }
 
         final File originFile = new File(originPath);
         final int uid = Binder.getCallingUid();
@@ -7717,7 +7730,7 @@
         if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
             user = UserHandle.ALL;
         } else {
-            user = new UserHandle(UserHandle.getUserId(uid));
+            user = new UserHandle(userId);
         }
 
         final int filteredInstallFlags;
@@ -12965,7 +12978,7 @@
     }
 
     /** Called by UserManagerService */
-    void cleanUpUserLILPw(int userHandle) {
+    void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
         mDirtyUsers.remove(userHandle);
         mSettings.removeUserLPw(userHandle);
         mPendingBroadcasts.remove(userHandle);
@@ -12976,6 +12989,50 @@
             mInstaller.removeUserDataDirs(userHandle);
         }
         mUserNeedsBadging.delete(userHandle);
+        removeUnusedPackagesLILPw(userManager, userHandle);
+    }
+
+    /**
+     * We're removing userHandle and would like to remove any downloaded packages
+     * that are no longer in use by any other user.
+     * @param userHandle the user being removed
+     */
+    private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) {
+        final boolean DEBUG_CLEAN_APKS = false;
+        int [] users = userManager.getUserIdsLPr();
+        Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
+        while (psit.hasNext()) {
+            PackageSetting ps = psit.next();
+            final String packageName = ps.pkg.packageName;
+            // Skip over if system app
+            if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                continue;
+            }
+            if (DEBUG_CLEAN_APKS) {
+                Slog.i(TAG, "Checking package " + packageName);
+            }
+            boolean keep = false;
+            for (int i = 0; i < users.length; i++) {
+                if (users[i] != userHandle && ps.getInstalled(users[i])) {
+                    keep = true;
+                    if (DEBUG_CLEAN_APKS) {
+                        Slog.i(TAG, "  Keeping package " + packageName + " for user "
+                                + users[i]);
+                    }
+                    break;
+                }
+            }
+            if (!keep) {
+                if (DEBUG_CLEAN_APKS) {
+                    Slog.i(TAG, "  Removing package " + packageName);
+                }
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        deletePackageX(packageName, userHandle, 0);
+                    } //end run
+                });
+            }
+        }
     }
 
     /** Called by UserManagerService */
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8ded7ca..2929939 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -293,6 +293,10 @@
     private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
         UserInfo user = getUserInfoLocked(userId);
         ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+        if (user == null) {
+            // Probably a dying user
+            return users;
+        }
         for (int i = 0; i < mUsers.size(); i++) {
             UserInfo profile = mUsers.valueAt(i);
             if (!isProfileOf(user, profile)) {
@@ -1280,7 +1284,7 @@
 
     private void removeUserStateLocked(final int userHandle) {
         // Cleanup package manager settings
-        mPm.cleanUpUserLILPw(userHandle);
+        mPm.cleanUpUserLILPw(this, userHandle);
 
         // Remove this user from the list
         mUsers.remove(userHandle);