Allow clearing instant app meta-data

The package manager has an API to clear the data of an app
which does not work for uninstalled instant apps for which
we store some meta-data (icon, title, cookie). This change
allows clearing the data of an uninstalled instant app.

Test: Instant cookie CTS tests use this API

bug:64517837

Change-Id: Ia929fead71b5ae786e88ddd0fa8e8a490d970dd0
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d432544..ff68e23 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2907,6 +2907,8 @@
     /**
      * @hide
      */
+    @RequiresPermission(anyOf={Manifest.permission.CLEAR_APP_USER_DATA,
+            Manifest.permission.ACCESS_INSTANT_APPS})
     public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
         try {
             return getService().clearApplicationUserData(packageName,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index daeb987..e90d904 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2207,7 +2207,12 @@
      * Note that the cleared package does <em>not</em>
      * receive this broadcast. The data contains the name of the package.
      * <ul>
-     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. If the
+     *      package whose data was cleared is an uninstalled instant app, then the UID
+     *      will be -1. The platform keeps some meta-data associated with instant apps
+     *      after they are uninstalled.
+     * <li> {@link #EXTRA_PACKAGE_NAME} containing the package name only if the cleared
+     *      data was for an instant app.
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 99700df..e8bade9 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -20,7 +20,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.ComponentInfoFlags;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManager.ResolveInfoFlags;
 import android.os.Bundle;
@@ -372,4 +371,11 @@
 
     /** Whether the binder caller can access instant apps. */
     public abstract boolean canAccessInstantApps(int callingUid, int userId);
+
+    /**
+     * Returns {@code true} if a given package has instant application meta-data.
+     * Otherwise, returns {@code false}. Meta-data is state (eg. cookie, app icon, etc)
+     * associated with an instant app. It may be kept after the instant app has been uninstalled.
+     */
+    public abstract boolean hasInstantApplicationMetadata(String packageName, int userId);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 161bf9a..7534c94 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5842,26 +5842,53 @@
         enforceNotIsolatedCaller("clearApplicationUserData");
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
-        userId = mUserController.handleIncomingUser(pid, uid, userId, false,
+        final int resolvedUserId = mUserController.handleIncomingUser(pid, uid, userId, false,
                 ALLOW_FULL_ONLY, "clearApplicationUserData", null);
 
+        final ApplicationInfo appInfo;
+        final boolean isInstantApp;
 
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
-            int pkgUid = -1;
             synchronized(this) {
+                // Instant packages are not protected
                 if (getPackageManagerInternalLocked().isPackageDataProtected(
-                        userId, packageName)) {
+                        resolvedUserId, packageName)) {
                     throw new SecurityException(
                             "Cannot clear data for a protected package: " + packageName);
                 }
 
+                ApplicationInfo applicationInfo = null;
                 try {
-                    pkgUid = pm.getPackageUid(packageName, MATCH_UNINSTALLED_PACKAGES, userId);
+                    applicationInfo = pm.getApplicationInfo(packageName,
+                            MATCH_UNINSTALLED_PACKAGES, resolvedUserId);
                 } catch (RemoteException e) {
+                    /* ignore */
                 }
-                if (pkgUid == -1) {
+                appInfo = applicationInfo;
+
+                final boolean clearingOwnUidData = appInfo != null && appInfo.uid == uid;
+
+                if (!clearingOwnUidData && checkComponentPermission(permission.CLEAR_APP_USER_DATA,
+                        pid, uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("PID " + pid + " does not have permission "
+                            + android.Manifest.permission.CLEAR_APP_USER_DATA + " to clear data"
+                            + " of package " + packageName);
+                }
+
+                final boolean hasInstantMetadata = getPackageManagerInternalLocked()
+                        .hasInstantApplicationMetadata(packageName, resolvedUserId);
+                final boolean isUninstalledAppWithoutInstantMetadata =
+                        (appInfo == null && !hasInstantMetadata);
+                isInstantApp = (appInfo != null && appInfo.isInstantApp())
+                        || hasInstantMetadata;
+                final boolean canAccessInstantApps = checkComponentPermission(
+                        permission.ACCESS_INSTANT_APPS, pid, uid, -1, true)
+                        == PackageManager.PERMISSION_GRANTED;
+
+                if (isUninstalledAppWithoutInstantMetadata || (isInstantApp
+                        && !canAccessInstantApps)) {
                     Slog.w(TAG, "Invalid packageName: " + packageName);
                     if (observer != null) {
                         try {
@@ -5872,45 +5899,45 @@
                     }
                     return false;
                 }
-                if (uid == pkgUid || checkComponentPermission(
-                        android.Manifest.permission.CLEAR_APP_USER_DATA,
-                        pid, uid, -1, true)
-                        == PackageManager.PERMISSION_GRANTED) {
-                    forceStopPackageLocked(packageName, pkgUid, "clear data");
-                } else {
-                    throw new SecurityException("PID " + pid + " does not have permission "
-                            + android.Manifest.permission.CLEAR_APP_USER_DATA + " to clear data"
-                                    + " of package " + packageName);
-                }
 
-                // Remove all tasks match the cleared application package and user
-                for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-                    final TaskRecord tr = mRecentTasks.get(i);
-                    final String taskPackageName =
-                            tr.getBaseIntent().getComponent().getPackageName();
-                    if (tr.userId != userId) continue;
-                    if (!taskPackageName.equals(packageName)) continue;
-                    mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
+                if (appInfo != null) {
+                    forceStopPackageLocked(packageName, appInfo.uid, "clear data");
+                    // Remove all tasks match the cleared application package and user
+                    for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
+                        final TaskRecord tr = mRecentTasks.get(i);
+                        final String taskPackageName =
+                                tr.getBaseIntent().getComponent().getPackageName();
+                        if (tr.userId != resolvedUserId) continue;
+                        if (!taskPackageName.equals(packageName)) continue;
+                        mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+                                REMOVE_FROM_RECENTS);
+                    }
                 }
             }
 
-            final int pkgUidF = pkgUid;
-            final int userIdF = userId;
             final IPackageDataObserver localObserver = new IPackageDataObserver.Stub() {
                 @Override
                 public void onRemoveCompleted(String packageName, boolean succeeded)
                         throws RemoteException {
-                    synchronized (ActivityManagerService.this) {
-                        finishForceStopPackageLocked(packageName, pkgUidF);
+                    if (appInfo != null) {
+                        synchronized (ActivityManagerService.this) {
+                            finishForceStopPackageLocked(packageName, appInfo.uid);
+                        }
                     }
-
                     final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
                             Uri.fromParts("package", packageName, null));
                     intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                    intent.putExtra(Intent.EXTRA_UID, pkgUidF);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(pkgUidF));
-                    broadcastIntentInPackage("android", SYSTEM_UID, intent,
-                            null, null, 0, null, null, null, null, false, false, userIdF);
+                    intent.putExtra(Intent.EXTRA_UID, (appInfo != null) ? appInfo.uid : -1);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+                    if (isInstantApp) {
+                        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+                        broadcastIntentInPackage("android", SYSTEM_UID, intent, null, null, 0,
+                                null, null, permission.ACCESS_INSTANT_APPS, null, false, false,
+                                resolvedUserId);
+                    } else {
+                        broadcastIntentInPackage("android", SYSTEM_UID, intent, null, null, 0,
+                                null, null, null, null, false, false, resolvedUserId);
+                    }
 
                     if (observer != null) {
                         observer.onRemoveCompleted(packageName, succeeded);
@@ -5920,16 +5947,18 @@
 
             try {
                 // Clear application user data
-                pm.clearApplicationUserData(packageName, localObserver, userId);
+                pm.clearApplicationUserData(packageName, localObserver, resolvedUserId);
 
-                synchronized(this) {
-                    // Remove all permissions granted from/to this package
-                    removeUriPermissionsForPackageLocked(packageName, userId, true);
+                if (appInfo != null) {
+                    synchronized (this) {
+                        // Remove all permissions granted from/to this package
+                        removeUriPermissionsForPackageLocked(packageName, resolvedUserId, true);
+                    }
+
+                    // Reset notification settings.
+                    INotificationManager inm = NotificationManager.getService();
+                    inm.clearData(packageName, appInfo.uid, uid == appInfo.uid);
                 }
-
-                // Reset notification settings.
-                INotificationManager inm = NotificationManager.getService();
-                inm.clearData(packageName, pkgUidF, uid == pkgUidF);
             } catch (RemoteException e) {
             }
         } finally {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 211a1c9..719c4d4 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -504,6 +504,11 @@
         }
     }
 
+    boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
+        return hasUninstalledInstantAppStateLPr(packageName, userId)
+                || hasInstantAppMetadataLPr(packageName, userId);
+    }
+
     public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
             @UserIdInt int userId) {
         removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
@@ -547,6 +552,33 @@
         }
     }
 
+    private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) {
+        if (mUninstalledInstantApps == null) {
+            return false;
+        }
+        final List<UninstalledInstantAppState> uninstalledAppStates =
+                mUninstalledInstantApps.get(userId);
+        if (uninstalledAppStates == null) {
+            return false;
+        }
+        final int appCount = uninstalledAppStates.size();
+        for (int i = 0; i < appCount; i++) {
+            final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
+            if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) {
+        final File instantAppDir = getInstantApplicationDir(packageName, userId);
+        return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists()
+                || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists()
+                || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists()
+                || peekInstantCookieFile(packageName, userId) != null;
+    }
+
     void pruneInstantApps() {
         final long maxInstalledCacheDuration = Settings.Global.getLong(
                 mService.mContext.getContentResolver(),
@@ -1071,7 +1103,7 @@
     }
 
     private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
-        return new File (getInstantApplicationsDir(userId), packageName);
+        return new File(getInstantApplicationsDir(userId), packageName);
     }
 
     private static void deleteDir(@NonNull File dir) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 90515df..12e2326 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -25171,6 +25171,13 @@
         public boolean canAccessInstantApps(int callingUid, int userId) {
             return PackageManagerService.this.canViewInstantApps(callingUid, userId);
         }
+
+        @Override
+        public boolean hasInstantApplicationMetadata(String packageName, int userId) {
+            synchronized (mPackages) {
+                return mInstantAppRegistry.hasInstantApplicationMetadataLPr(packageName, userId);
+            }
+        }
     }
 
     @Override