diff options
| author | 2017-05-24 00:27:21 -0700 | |
|---|---|---|
| committer | 2017-05-31 02:30:41 +0000 | |
| commit | f36d53cbfc34dcadfe156f3037bf40b4908142a8 (patch) | |
| tree | 5d7283806b018b40cd079b687e717a1e6906f597 | |
| parent | ddd5458092fe18723f42df68ca27525aacc1f8c1 (diff) | |
Prune unused static libs and instant apps if space needed
We are caching unused static shared libs and instant apps
(installed and uninstalled) opportunistically. If space is
needed we delete these to free up space.
Test: manual
bug:62045000
Change-Id: Id992dee5c7c6e36b8e8b81050602dbc4eeafb0f9
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 |