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