diff options
6 files changed, 351 insertions, 49 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3cd54b8af39e..6a17ed1500bd 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9817,13 +9817,49 @@ public final class Settings { public static final String ENABLE_EPHEMERAL_FEATURE = "enable_ephemeral_feature"; /** - * The duration for caching uninstalled instant apps. + * The min period for caching installed instant apps in milliseconds. * <p> * Type: long * @hide */ - public static final String UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS = - "uninstalled_instant_app_cache_duration_millis"; + public static final String INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = + "installed_instant_app_min_cache_period"; + + /** + * The max period for caching installed instant apps in milliseconds. + * <p> + * Type: long + * @hide + */ + public static final String INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = + "installed_instant_app_max_cache_period"; + + /** + * The min period for caching uninstalled instant apps in milliseconds. + * <p> + * Type: long + * @hide + */ + public static final String UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = + "uninstalled_instant_app_min_cache_period"; + + /** + * The max period for caching uninstalled instant apps in milliseconds. + * <p> + * Type: long + * @hide + */ + public static final String UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = + "uninstalled_instant_app_max_cache_period"; + + /** + * The min period for caching unused static shared libs in milliseconds. + * <p> + * Type: long + * @hide + */ + public static final String UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD = + "unused_static_shared_lib_min_cache_period"; /** * Allows switching users when system user is locked. diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index 8b73daf47f0f..e212c432558c 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -310,7 +310,7 @@ message GlobalSettingsProto { SettingProto lte_service_forced = 265; SettingProto ephemeral_cookie_max_size_bytes = 266; SettingProto enable_ephemeral_feature = 267; - SettingProto uninstalled_ephemeral_app_cache_duration_millis = 268; + SettingProto installed_instant_app_min_cache_period = 268; SettingProto allow_user_switching_when_system_user_locked = 269; SettingProto boot_count = 270; SettingProto safe_boot_disallowed = 271; @@ -331,6 +331,10 @@ message GlobalSettingsProto { SettingProto network_recommendations_package = 286; SettingProto bluetooth_a2dp_supports_optional_codecs_prefix = 287; SettingProto bluetooth_a2dp_optional_codecs_enabled_prefix = 288; + SettingProto installed_instant_app_max_cache_period = 289; + SettingProto uninstalled_instant_app_min_cache_period = 290; + SettingProto uninstalled_instant_app_max_cache_period = 291; + SettingProto unused_static_shared_lib_min_cache_period = 292; } message SecureSettingsProto { diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 54d5285e3224..d875ed4e5ec5 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -334,7 +334,11 @@ public class SettingsBackupTest { Settings.Global.TRUSTED_SOUND, Settings.Global.TZINFO_UPDATE_CONTENT_URL, Settings.Global.TZINFO_UPDATE_METADATA_URL, - Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS, + Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, + Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, + Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, Settings.Global.UNLOCK_SOUND, Settings.Global.USE_GOOGLE_MAIL, Settings.Global.VT_IMS_ENABLED, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 930935914967..b328933cd1c5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -888,8 +888,20 @@ class SettingsProtoDumpUtil { Settings.Global.ENABLE_EPHEMERAL_FEATURE, GlobalSettingsProto.ENABLE_EPHEMERAL_FEATURE); dumpSetting(s, p, - Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS, - GlobalSettingsProto.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS); + Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD); + dumpSetting(s, p, + Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, + GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); + dumpSetting(s, p, + Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + GlobalSettingsProto.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD); + dumpSetting(s, p, + Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, + GlobalSettingsProto.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); + dumpSetting(s, p, + Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, + GlobalSettingsProto.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD); dumpSetting(s, p, Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, GlobalSettingsProto.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED); diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index b1659841d5ac..211a1c9f3edf 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Intent; import android.content.pm.InstantAppInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -32,6 +33,8 @@ import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.UserHandle; +import android.os.storage.StorageManager; import android.provider.Settings; import android.util.ArrayMap; import android.util.AtomicFile; @@ -76,7 +79,16 @@ class InstantAppRegistry { private static final String LOG_TAG = "InstantAppRegistry"; - private static final long DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS = + static final long DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = + DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */ + + private static final long DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = + DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ + + static final long DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = + DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */ + + private static final long DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ private static final String INSTANT_APPS_FOLDER = "instant"; @@ -535,46 +547,195 @@ class InstantAppRegistry { } } - public void pruneInstantAppsLPw() { - // For now we prune only state for uninstalled instant apps - final long maxCacheDurationMillis = Settings.Global.getLong( + void pruneInstantApps() { + final long maxInstalledCacheDuration = Settings.Global.getLong( mService.mContext.getContentResolver(), - Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS, - DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS); - - for (int userId : UserManagerService.getInstance().getUserIds()) { - // Prune in-memory state - removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> { - final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp; - return (elapsedCachingMillis > maxCacheDurationMillis); - }, userId); - - // Prune on-disk state - File instantAppsDir = getInstantApplicationsDir(userId); - if (!instantAppsDir.exists()) { - continue; + Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, + DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); + + final long maxUninstalledCacheDuration = Settings.Global.getLong( + mService.mContext.getContentResolver(), + Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, + DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); + + try { + pruneInstantApps(Long.MAX_VALUE, + maxInstalledCacheDuration, maxUninstalledCacheDuration); + } catch (IOException e) { + Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e); + } + } + + boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) { + try { + return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE); + } catch (IOException e) { + Slog.e(LOG_TAG, "Error pruning installed instant apps", e); + return false; + } + } + + boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) { + try { + return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration); + } catch (IOException e) { + Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e); + return false; + } + } + + /** + * Prunes instant apps until there is enough <code>neededSpace</code>. Both + * installed and uninstalled instant apps are pruned that are older than + * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code> + * respectively. All times are in milliseconds. + * + * @param neededSpace The space to ensure is free. + * @param maxInstalledCacheDuration The max duration for caching installed apps in millis. + * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis. + * @return Whether enough space was freed. + * + * @throws IOException + */ + private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration, + long maxUninstalledCacheDuration) throws IOException { + final StorageManager storage = mService.mContext.getSystemService(StorageManager.class); + final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); + + if (file.getUsableSpace() >= neededSpace) { + return true; + } + + List<String> packagesToDelete = null; + + final int[] allUsers; + final long now = System.currentTimeMillis(); + + // Prune first installed instant apps + synchronized (mService.mPackages) { + allUsers = PackageManagerService.sUserManager.getUserIds(); + + final int packageCount = mService.mPackages.size(); + for (int i = 0; i < packageCount; i++) { + final PackageParser.Package pkg = mService.mPackages.valueAt(i); + if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) { + continue; + } + if (!(pkg.mExtras instanceof PackageSetting)) { + continue; + } + final PackageSetting ps = (PackageSetting) pkg.mExtras; + boolean installedOnlyAsInstantApp = false; + for (int userId : allUsers) { + if (ps.getInstalled(userId)) { + if (ps.getInstantApp(userId)) { + installedOnlyAsInstantApp = true; + } else { + installedOnlyAsInstantApp = false; + break; + } + } + } + if (installedOnlyAsInstantApp) { + if (packagesToDelete == null) { + packagesToDelete = new ArrayList<>(); + } + packagesToDelete.add(pkg.packageName); + } } - File[] files = instantAppsDir.listFiles(); - if (files == null) { - continue; + + if (packagesToDelete != null) { + packagesToDelete.sort((String lhs, String rhs) -> { + final PackageParser.Package lhsPkg = mService.mPackages.get(lhs); + final PackageParser.Package rhsPkg = mService.mPackages.get(rhs); + if (lhsPkg == null && rhsPkg == null) { + return 0; + } else if (lhsPkg == null) { + return -1; + } else if (rhsPkg == null) { + return 1; + } else { + if (lhsPkg.getLatestPackageUseTimeInMills() > + rhsPkg.getLatestPackageUseTimeInMills()) { + return 1; + } else if (lhsPkg.getLatestPackageUseTimeInMills() < + rhsPkg.getLatestPackageUseTimeInMills()) { + return -1; + } else { + if (lhsPkg.mExtras instanceof PackageSetting + && rhsPkg.mExtras instanceof PackageSetting) { + final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras; + final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras; + if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) { + return 1; + } else { + return -1; + } + } else { + return 0; + } + } + } + }); } - for (File instantDir : files) { - if (!instantDir.isDirectory()) { - continue; + } + + if (packagesToDelete != null) { + final int packageCount = packagesToDelete.size(); + for (int i = 0; i < packageCount; i++) { + final String packageToDelete = packagesToDelete.get(i); + if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST, + UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS) + == PackageManager.DELETE_SUCCEEDED) { + if (file.getUsableSpace() >= neededSpace) { + return true; + } } + } + } - File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE); - if (!metadataFile.exists()) { + // Prune uninstalled instant apps + synchronized (mService.mPackages) { + // TODO: Track last used time for uninstalled instant apps for better pruning + for (int userId : UserManagerService.getInstance().getUserIds()) { + // Prune in-memory state + removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> { + final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp; + return (elapsedCachingMillis > maxUninstalledCacheDuration); + }, userId); + + // Prune on-disk state + File instantAppsDir = getInstantApplicationsDir(userId); + if (!instantAppsDir.exists()) { + continue; + } + File[] files = instantAppsDir.listFiles(); + if (files == null) { continue; } + for (File instantDir : files) { + if (!instantDir.isDirectory()) { + continue; + } + + File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE); + if (!metadataFile.exists()) { + continue; + } - final long elapsedCachingMillis = System.currentTimeMillis() - - metadataFile.lastModified(); - if (elapsedCachingMillis > maxCacheDurationMillis) { - deleteDir(instantDir); + final long elapsedCachingMillis = System.currentTimeMillis() + - metadataFile.lastModified(); + if (elapsedCachingMillis > maxUninstalledCacheDuration) { + deleteDir(instantDir); + if (file.getUsableSpace() >= neededSpace) { + return true; + } + } } } } + + return false; } private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr( diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5ddd2f10e0c3..4f784d1164ef 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -875,9 +875,9 @@ public class PackageManagerService extends IPackageManager.Stub new ParallelPackageParserCallback(); public static final class SharedLibraryEntry { - public final String path; - public final String apk; - public final SharedLibraryInfo info; + public final @Nullable String path; + public final @Nullable String apk; + public final @NonNull SharedLibraryInfo info; SharedLibraryEntry(String _path, String _apk, String name, int version, int type, String declaringPackageName, int declaringPackageVersionCode) { @@ -1312,6 +1312,9 @@ public class PackageManagerService extends IPackageManager.Stub // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; + private static final long DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD = + 2 * 60 * 60 * 1000L; /* two hours */ + static UserManagerService sUserManager; // Stores a list of users whose package restrictions file needs to be updated @@ -4235,9 +4238,24 @@ public class PackageManagerService extends IPackageManager.Stub } if (file.getUsableSpace() >= bytes) return; - // 5. Consider shared libraries with refcount=0 and age>2h + // 5. Consider shared libraries with refcount=0 and age>min cache period + if (internalVolume && pruneUnusedStaticSharedLibraries(bytes, + android.provider.Settings.Global.getLong(mContext.getContentResolver(), + Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, + DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) { + return; + } + // 6. Consider dexopt output (aggressive only) - // 7. Consider ephemeral apps not used in last week + // TODO: Implement + + // 7. Consider installed instant apps unused longer than min cache period + if (internalVolume && mInstantAppRegistry.pruneInstalledInstantApps(bytes, + android.provider.Settings.Global.getLong(mContext.getContentResolver(), + Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + InstantAppRegistry.DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { + return; + } // 8. Consider cached app data (below quotas) try { @@ -4248,8 +4266,15 @@ public class PackageManagerService extends IPackageManager.Stub if (file.getUsableSpace() >= bytes) return; // 9. Consider DropBox entries - // 10. Consider ephemeral cookies + // TODO: Implement + // 10. Consider instant meta-data (uninstalled apps) older that min cache period + if (internalVolume && mInstantAppRegistry.pruneUninstalledInstantApps(bytes, + android.provider.Settings.Global.getLong(mContext.getContentResolver(), + Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { + return; + } } else { try { mInstaller.freeCache(volumeUuid, bytes, 0); @@ -4261,6 +4286,69 @@ public class PackageManagerService extends IPackageManager.Stub throw new IOException("Failed to free " + bytes + " on storage device at " + file); } + private boolean pruneUnusedStaticSharedLibraries(long neededSpace, long maxCachePeriod) + throws IOException { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); + + List<VersionedPackage> packagesToDelete = null; + final long now = System.currentTimeMillis(); + + synchronized (mPackages) { + final int[] allUsers = sUserManager.getUserIds(); + final int libCount = mSharedLibraries.size(); + for (int i = 0; i < libCount; i++) { + final SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.valueAt(i); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int j = 0; j < versionCount; j++) { + SharedLibraryInfo libInfo = versionedLib.valueAt(j).info; + // Skip packages that are not static shared libs. + if (!libInfo.isStatic()) { + break; + } + // Important: We skip static shared libs used for some user since + // in such a case we need to keep the APK on the device. The check for + // a lib being used for any user is performed by the uninstall call. + final VersionedPackage declaringPackage = libInfo.getDeclaringPackage(); + // Resolve the package name - we use synthetic package names internally + final String internalPackageName = resolveInternalPackageNameLPr( + declaringPackage.getPackageName(), declaringPackage.getVersionCode()); + final PackageSetting ps = mSettings.getPackageLPr(internalPackageName); + // Skip unused static shared libs cached less than the min period + // to prevent pruning a lib needed by a subsequently installed package. + if (ps == null || now - ps.lastUpdateTime < maxCachePeriod) { + continue; + } + if (packagesToDelete == null) { + packagesToDelete = new ArrayList<>(); + } + packagesToDelete.add(new VersionedPackage(internalPackageName, + declaringPackage.getVersionCode())); + } + } + } + + if (packagesToDelete != null) { + final int packageCount = packagesToDelete.size(); + for (int i = 0; i < packageCount; i++) { + final VersionedPackage pkgToDelete = packagesToDelete.get(i); + // Delete the package synchronously (will fail of the lib used for any user). + if (deletePackageX(pkgToDelete.getPackageName(), pkgToDelete.getVersionCode(), + UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS) + == PackageManager.DELETE_SUCCEEDED) { + if (volume.getUsableSpace() >= neededSpace) { + return true; + } + } + } + } + + return false; + } + /** * Update given flags based on encryption status of current user. */ @@ -10650,8 +10738,7 @@ public class PackageManagerService extends IPackageManager.Stub final int versionCount = versionedLib.size(); for (int i = 0; i < versionCount; i++) { SharedLibraryInfo libInfo = versionedLib.valueAt(i).info; - // TODO: We will change version code to long, so in the new API it is long - final int libVersionCode = (int) libInfo.getDeclaringPackage() + final int libVersionCode = libInfo.getDeclaringPackage() .getVersionCode(); if (libInfo.getVersion() < pkg.staticSharedLibVersion) { minVersionCode = Math.max(minVersionCode, libVersionCode + 1); @@ -18488,7 +18575,7 @@ public class PackageManagerService extends IPackageManager.Stub * persisting settings for later use * sending a broadcast if necessary */ - private int deletePackageX(String packageName, int versionCode, int userId, int deleteFlags) { + int deletePackageX(String packageName, int versionCode, int userId, int deleteFlags) { final PackageRemovedInfo info = new PackageRemovedInfo(this); final boolean res; @@ -24273,9 +24360,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); @Override public void pruneInstantApps() { - synchronized (mPackages) { - mInstantAppRegistry.pruneInstantAppsLPw(); - } + mInstantAppRegistry.pruneInstantApps(); } @Override |