Prune unused static libs and instant apps if space needed

am: f36d53cbfc

Change-Id: I0fdca2a5069b6f3572a9cb5fe0d3ead0442b7b46
This commit is contained in:
Svet Ganov
2017-05-31 03:52:16 +00:00
committed by android-build-merger
6 changed files with 351 additions and 49 deletions

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);
Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
for (int userId : UserManagerService.getInstance().getUserIds()) {
// Prune in-memory state
removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
return (elapsedCachingMillis > maxCacheDurationMillis);
}, userId);
final long maxUninstalledCacheDuration = Settings.Global.getLong(
mService.mContext.getContentResolver(),
Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
// 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()) {
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;
}
File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
if (!metadataFile.exists()) {
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);
}
}
final long elapsedCachingMillis = System.currentTimeMillis()
- metadataFile.lastModified();
if (elapsedCachingMillis > maxCacheDurationMillis) {
deleteDir(instantDir);
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;
}
}
}
});
}
}
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;
}
}
}
}
// 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 > maxUninstalledCacheDuration) {
deleteDir(instantDir);
if (file.getUsableSpace() >= neededSpace) {
return true;
}
}
}
}
}
return false;
}
private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(

View File

@@ -876,9 +876,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) {
@@ -1313,6 +1313,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.
*/
@@ -10658,8 +10746,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);
@@ -18505,7 +18592,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;
@@ -24290,9 +24377,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
@Override
public void pruneInstantApps() {
synchronized (mPackages) {
mInstantAppRegistry.pruneInstantAppsLPw();
}
mInstantAppRegistry.pruneInstantApps();
}
@Override