From 246dccf9327631597767afe418ce43ae6d07d102 Mon Sep 17 00:00:00 2001 From: Shubham Ajmera Date: Wed, 24 May 2017 17:46:36 -0700 Subject: [PATCH] Reduce app size by downgrading inactive apps This will trigger when the device will have low space. Active apps here refer to the apps which were either active in foregrond or in background and also used by other packages. Apps which are inactive for X days downgraded to verify. X is determined by sysprop pm.dexopt.unopt_after_inactive_days If the system properties are not set, no effect will take place. The above operations will take place in background dexopt service. If user uses the app again, it will again be speed-compiled when background dexopt service starts next time. Bug: 36598475 Test: manual * Remove the check in the code that allows downgrade only when the space is low on the device. * adb root * Set pm.dexopt_unopt_after_inactive_days to 600 * Make sure the current time of the device is correctly set * Install 2 non system apps - B, C * Downgrade B to extract * Upgrade a system apps to speed-profile - E * Downgrade a system app to quicken - G * adb shell cmd package bg-dexopt-job Expected Results: * Extract - B * Verify - C * There should not be any entries for apps E an G in dalvik_cache Change-Id: I68f9f617d6722a7ba8b00aa2181cb38a165cfc51 --- .../android/content/pm/IPackageManager.aidl | 2 +- .../com/android/internal/os/ZygoteInit.java | 5 +- .../server/pm/BackgroundDexOptService.java | 88 +++++++++++-- .../java/com/android/server/pm/Installer.java | 4 +- .../android/server/pm/OtaDexoptService.java | 42 ++----- .../server/pm/PackageDexOptimizer.java | 34 +++-- .../server/pm/PackageManagerService.java | 119 ++++++++++++++---- .../PackageManagerServiceCompilerMapping.java | 2 +- .../server/pm/PackageManagerServiceUtils.java | 39 ++++++ .../com/android/server/pm/dex/DexManager.java | 12 +- 10 files changed, 257 insertions(+), 90 deletions(-) diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index e800e8857cb4e..afb798cdb9885 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -513,7 +513,7 @@ interface IPackageManager { * configuration. */ boolean performDexOpt(String packageName, boolean checkProfiles, - int compileReason, boolean force, boolean bootComplete); + int compileReason, boolean force, boolean bootComplete, boolean downgrade); /** * Ask the package manager to perform a dex-opt with the given compiler filter. diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 07edc72bcf4bd..0dbe971ed3f57 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -548,7 +548,8 @@ public class ZygoteInit { int dexoptNeeded; try { dexoptNeeded = DexFile.getDexOptNeeded( - classPathElement, instructionSet, systemServerFilter, false /* newProfile */); + classPathElement, instructionSet, systemServerFilter, + false /* newProfile */, false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); @@ -572,7 +573,7 @@ public class ZygoteInit { try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, - uuid, sharedLibraries, seInfo); + uuid, sharedLibraries, seInfo, false /* downgrade */); } catch (RemoteException | ServiceSpecificException e) { // Ignore (but log), we need this on the classpath for fallback mode. Log.w(TAG, "Failed compiling classpath element for system server: " diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 6749afb4fee01..34092ad6607a5 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; -import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -40,6 +39,7 @@ import com.android.server.LocalServices; import com.android.server.PinnerService; import java.io.File; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; @@ -73,6 +73,9 @@ public class BackgroundDexOptService extends JobService { // Optimizations should be aborted. No space left on device. private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3; + // Used for calculating space threshold for downgrading unused apps. + private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2; + /** * Set of failed packages remembered across job runs. */ @@ -92,6 +95,9 @@ public class BackgroundDexOptService extends JobService { private final File mDataDir = Environment.getDataDirectory(); + private static final long mDowngradeUnusedAppsThresholdInMillis = + getDowngradeUnusedAppsThresholdInMillis(); + public static void schedule(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -215,7 +221,8 @@ public class BackgroundDexOptService extends JobService { /* checkProfiles */ false, PackageManagerService.REASON_BOOT, /* force */ false, - /* bootComplete */ true); + /* bootComplete */ true, + /* downgrade */ false); if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); } @@ -243,7 +250,8 @@ public class BackgroundDexOptService extends JobService { } // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). - private int idleOptimization(PackageManagerService pm, ArraySet pkgs, Context context) { + private int idleOptimization(PackageManagerService pm, ArraySet pkgs, + Context context) { Log.i(TAG, "Performing idle optimizations"); // If post-boot update is still running, request that it exits early. mExitPostBootUpdate.set(true); @@ -274,9 +282,16 @@ public class BackgroundDexOptService extends JobService { long lowStorageThreshold, boolean is_for_primary_dex, ArraySet failedPackageNames) { ArraySet updatedPackages = new ArraySet<>(); + Set unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); + // Only downgrade apps when space is low on device. + // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean + // up disk before user hits the actual lowStorageThreshold. + final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * + lowStorageThreshold; + boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); for (String pkg : pkgs) { int abort_code = abortIdleOptimizations(lowStorageThreshold); - if (abort_code != OPTIMIZE_CONTINUE) { + if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return abort_code; } @@ -284,30 +299,57 @@ public class BackgroundDexOptService extends JobService { if (failedPackageNames.contains(pkg)) { // Skip previously failing package continue; - } else { - // Conservatively add package to the list of failing ones in case performDexOpt - // never returns. - failedPackageNames.add(pkg); } } + int reason; + boolean downgrade; + // Downgrade unused packages. + if (unusedPackages.contains(pkg) && shouldDowngrade) { + // This applies for system apps or if packages location is not a directory, i.e. + // monolithic install. + if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) { + // For apps that don't have the oat directory, instead of downgrading, + // remove their compiler artifacts from dalvik cache. + pm.deleteOatArtifactsOfPackage(pkg); + continue; + } else { + reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; + downgrade = true; + } + } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) { + reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; + downgrade = false; + } else { + // can't dexopt because of low space. + continue; + } + + synchronized (failedPackageNames) { + // Conservatively add package to the list of failing ones in case + // performDexOpt never returns. + failedPackageNames.add(pkg); + } + // Optimize package if needed. Note that there can be no race between // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. boolean success; if (is_for_primary_dex) { int result = pm.performDexOptWithStatus(pkg, /* checkProfiles */ true, - PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false, - /* bootComplete */ true); + reason, + false /* forceCompile*/, + true /* bootComplete */, + downgrade); success = result != PackageDexOptimizer.DEX_OPT_FAILED; if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); } } else { success = pm.performDexOptSecondary(pkg, - PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false); + reason, + false /* force */, + downgrade); } if (success) { // Dexopt succeeded, remove package from the list of failing ones. @@ -347,6 +389,16 @@ public class BackgroundDexOptService extends JobService { return OPTIMIZE_CONTINUE; } + // Evaluate whether apps should be downgraded. + private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) { + long usableSpace = mDataDir.getUsableSpace(); + if (usableSpace < lowStorageThresholdForDowngrade) { + return true; + } + + return false; + } + /** * Execute the idle optimizations immediately. */ @@ -415,4 +467,14 @@ public class BackgroundDexOptService extends JobService { pinnerService.update(updatedPackages); } } + + private static long getDowngradeUnusedAppsThresholdInMillis() { + final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; + String sysPropValue = SystemProperties.get(sysPropKey); + if (sysPropValue == null || sysPropValue.isEmpty()) { + Log.w(TAG, "SysProp " + sysPropKey + " not set"); + return Long.MAX_VALUE; + } + return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue)); + } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index bd765b49a4ab6..371b3ef5f5551 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -279,13 +279,13 @@ public class Installer extends SystemService { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries, - @Nullable String seInfo) + @Nullable String seInfo, boolean downgrade) throws InstallerException { assertValidInstructionSet(instructionSet); if (!checkBeforeRemote()) return; try { mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath, - dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo); + dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 2a45d156ed420..4ff6cbf1e1c88 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -30,7 +30,6 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.storage.StorageManager; -import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -40,7 +39,6 @@ import com.android.server.pm.Installer.InstallerException; import java.io.File; import java.io.FileDescriptor; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; @@ -152,7 +150,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: " + PackageManagerServiceUtils.packagesToString(others)); for (PackageParser.Package pkg : others) { - deleteOatArtifactsOfPackage(pkg); + mPackageManagerService.deleteOatArtifactsOfPackage(pkg.packageName); } } long spaceAvailableNow = getAvailableSpace(); @@ -242,30 +240,6 @@ public class OtaDexoptService extends IOtaDexopt.Stub { return usableSpace - lowThreshold; } - private static String getOatDir(PackageParser.Package pkg) { - if (!pkg.canHaveOatDir()) { - return null; - } - File codePath = new File(pkg.codePath); - if (codePath.isDirectory()) { - return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath(); - } - return null; - } - - private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) { - String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); - for (String codePath : pkg.getAllCodePaths()) { - for (String isa : instructionSets) { - try { - mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg)); - } catch (InstallerException e) { - Log.e(TAG, "Failed deleting oat files for " + codePath, e); - } - } - } - } - /** * Generate all dexopt commands for the given package. */ @@ -285,11 +259,12 @@ public class OtaDexoptService extends IOtaDexopt.Stub { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, - @Nullable String sharedLibraries, @Nullable String seInfo) throws InstallerException { + @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade) + throws InstallerException { final StringBuilder builder = new StringBuilder(); - // The version. Right now it's 2. - builder.append("2 "); + // The version. Right now it's 3. + builder.append("3 "); builder.append("dexopt"); @@ -304,6 +279,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { encodeParameter(builder, volumeUuid); encodeParameter(builder, sharedLibraries); encodeParameter(builder, seInfo); + encodeParameter(builder, downgrade); commands.add(builder.toString()); } @@ -343,12 +319,14 @@ public class OtaDexoptService extends IOtaDexopt.Stub { getCompilerFilterForReason(compilationReason), null /* CompilerStats.PackageStats */, mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), - true /* bootComplete */); + true /* bootComplete */, + false /* downgrade */); mPackageManagerService.getDexManager().dexoptSecondaryDex(pkg.packageName, getCompilerFilterForReason(compilationReason), false /* force */, - false /* compileOnlySharedDex */); + false /* compileOnlySharedDex */, + false /* downgrade */); return commands; } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 2b7bd3442cc7a..3eaeb6df64dd9 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -114,7 +114,7 @@ public class PackageDexOptimizer { int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries, String[] instructionSets, boolean checkProfiles, String targetCompilationFilter, CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps, - boolean bootComplete) { + boolean bootComplete, boolean downgrade) { if (!canOptimizePackage(pkg)) { return DEX_OPT_SKIPPED; } @@ -122,7 +122,8 @@ public class PackageDexOptimizer { final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid); try { return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles, - targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete); + targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete, + downgrade); } finally { releaseWakeLockLI(acquireTime); } @@ -137,7 +138,7 @@ public class PackageDexOptimizer { private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries, String[] targetInstructionSets, boolean checkForProfileUpdates, String targetCompilerFilter, CompilerStats.PackageStats packageStats, - boolean isUsedByOtherApps, boolean bootComplete) { + boolean isUsedByOtherApps, boolean bootComplete, boolean downgrade) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); @@ -174,7 +175,8 @@ public class PackageDexOptimizer { } for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated, - sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats); + sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats, + downgrade); // The end result is: // - FAILED if any path failed, // - PERFORMED if at least one path needed compilation, @@ -198,8 +200,8 @@ public class PackageDexOptimizer { @GuardedBy("mInstallLock") private int dexOptPath(PackageParser.Package pkg, String path, String isa, String compilerFilter, boolean profileUpdated, String sharedLibrariesPath, - int dexoptFlags, int uid, CompilerStats.PackageStats packageStats) { - int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated); + int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) { + int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated, downgrade); if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) { return DEX_OPT_SKIPPED; } @@ -218,8 +220,12 @@ public class PackageDexOptimizer { try { long startTime = System.currentTimeMillis(); + // TODO: Consider adding 2 different APIs for primary and secondary dexopt. + // installd only uses downgrade flag for secondary dex files and ignores it for + // primary dex files. mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags, - compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo); + compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo, + false /* downgrade*/); if (packageStats != null) { long endTime = System.currentTimeMillis(); @@ -247,12 +253,12 @@ public class PackageDexOptimizer { * that seems wasteful. */ public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set isas, - String compilerFilter, boolean isUsedByOtherApps) { + String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) { synchronized (mInstallLock) { final long acquireTime = acquireWakeLockLI(info.uid); try { return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter, - isUsedByOtherApps); + isUsedByOtherApps, downgrade); } finally { releaseWakeLockLI(acquireTime); } @@ -294,7 +300,7 @@ public class PackageDexOptimizer { @GuardedBy("mInstallLock") private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set isas, - String compilerFilter, boolean isUsedByOtherApps) { + String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) { compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. // Secondary dex files are currently not compiled at boot. @@ -324,7 +330,8 @@ public class PackageDexOptimizer { // TODO(calin): maybe add a separate call. mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, /*oatDir*/ null, dexoptFlags, - compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser); + compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser, + downgrade); } return DEX_OPT_PERFORMED; @@ -425,10 +432,11 @@ public class PackageDexOptimizer { * configuration (isa, compiler filter, profile). */ private int getDexoptNeeded(String path, String isa, String compilerFilter, - boolean newProfile) { + boolean newProfile, boolean downgrade) { int dexoptNeeded; try { - dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile); + dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile, + downgrade); } catch (IOException ioe) { Slog.w(TAG, "IOException reading apk: " + path, ioe); return DEX_OPT_FAILED; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 700d3c2ef805c..191d146cd6aad 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -283,6 +283,7 @@ import com.android.server.pm.PermissionsState.PermissionState; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.PackageDexUsage; import com.android.server.storage.DeviceStorageMonitorInternal; import dalvik.system.CloseGuard; @@ -561,8 +562,9 @@ public class PackageManagerService extends IPackageManager.Stub public static final int REASON_INSTALL = 2; public static final int REASON_BACKGROUND_DEXOPT = 3; public static final int REASON_AB_OTA = 4; + public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5; - public static final int REASON_LAST = REASON_AB_OTA; + public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE; /** All dangerous permission names in the same order as the events in MetricsEvent */ private static final List ALL_DANGEROUS_PERMISSIONS = Arrays.asList( @@ -9344,7 +9346,8 @@ public class PackageManagerService extends IPackageManager.Stub false /* checkProfiles */, compilerFilter, false /* force */, - bootComplete); + bootComplete, + false /* downgrade */); if (pkg.isSystemApp()) { // Only dexopt shared secondary dex files belonging to system apps to not slow down @@ -9352,7 +9355,8 @@ public class PackageManagerService extends IPackageManager.Stub mDexManager.dexoptSecondaryDex(pkg.packageName, compilerFilter, false /* force */, - true /* compileOnlySharedDex */); + true /* compileOnlySharedDex */, + false /* downgrade */); } // TODO(shubhamajmera): Record secondary dexopt stats. @@ -9437,14 +9441,16 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean performDexOpt(String packageName, - boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) { + boolean checkProfiles, int compileReason, boolean force, boolean bootComplete, + boolean downgrade) { if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { return false; } int dexoptStatus = performDexOptWithStatus( - packageName, checkProfiles, compileReason, force, bootComplete); + packageName, checkProfiles, compileReason, force, bootComplete, + downgrade); return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED; } @@ -9455,9 +9461,10 @@ public class PackageManagerService extends IPackageManager.Stub * {@link PackageDexOptimizer#DEX_OPT_FAILED} */ /* package */ int performDexOptWithStatus(String packageName, - boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) { + boolean checkProfiles, int compileReason, boolean force, boolean bootComplete, + boolean downgrade) { return performDexOptTraced(packageName, checkProfiles, - getCompilerFilterForReason(compileReason), force, bootComplete); + getCompilerFilterForReason(compileReason), force, bootComplete, downgrade); } @Override @@ -9470,17 +9477,17 @@ public class PackageManagerService extends IPackageManager.Stub return false; } int dexOptStatus = performDexOptTraced(packageName, checkProfiles, - targetCompilerFilter, force, bootComplete); + targetCompilerFilter, force, bootComplete, false /* downgrade */); return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED; } private int performDexOptTraced(String packageName, boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { + boolean bootComplete, boolean downgrade) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); try { return performDexOptInternal(packageName, checkProfiles, - targetCompilerFilter, force, bootComplete); + targetCompilerFilter, force, bootComplete, downgrade); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -9490,7 +9497,7 @@ public class PackageManagerService extends IPackageManager.Stub // if the package can now be considered up to date for the given filter. private int performDexOptInternal(String packageName, boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { + boolean bootComplete, boolean downgrade) { PackageParser.Package p; synchronized (mPackages) { p = mPackages.get(packageName); @@ -9505,7 +9512,7 @@ public class PackageManagerService extends IPackageManager.Stub try { synchronized (mInstallLock) { return performDexOptInternalWithDependenciesLI(p, checkProfiles, - targetCompilerFilter, force, bootComplete); + targetCompilerFilter, force, bootComplete, downgrade); } } finally { Binder.restoreCallingIdentity(callingId); @@ -9526,7 +9533,7 @@ public class PackageManagerService extends IPackageManager.Stub private int performDexOptInternalWithDependenciesLI(PackageParser.Package p, boolean checkProfiles, String targetCompilerFilter, - boolean force, boolean bootComplete) { + boolean force, boolean bootComplete, boolean downgrade) { // Select the dex optimizer based on the force parameter. // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to // allocate an object here. @@ -9551,12 +9558,13 @@ public class PackageManagerService extends IPackageManager.Stub targetCompilerFilter, getOrCreateCompilerPackageStats(depPackage), true /* isUsedByOtherApps */, - bootComplete); + bootComplete, + downgrade); } } return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles, targetCompilerFilter, getOrCreateCompilerPackageStats(p), - mDexManager.isUsedByOtherApps(p.packageName), bootComplete); + mDexManager.isUsedByOtherApps(p.packageName), bootComplete, downgrade); } // Performs dexopt on the used secondary dex files belonging to the given package. @@ -9571,12 +9579,12 @@ public class PackageManagerService extends IPackageManager.Stub return false; } return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force, - /* compileOnlySharedDex*/ false); + false /* compileOnlySharedDex */, false /* downgrade */); } public boolean performDexOptSecondary(String packageName, int compileReason, - boolean force) { - return mDexManager.dexoptSecondaryDex(packageName, compileReason, force); + boolean force, boolean downgrade) { + return mDexManager.dexoptSecondaryDex(packageName, compileReason, force, downgrade); } /** @@ -9755,7 +9763,8 @@ public class PackageManagerService extends IPackageManager.Stub final int res = performDexOptInternalWithDependenciesLI(pkg, false /* checkProfiles */, getDefaultCompilerFilter(), true /* force */, - true /* bootComplete */); + true /* bootComplete */, + false /* downgrade */); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { @@ -16176,7 +16185,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void removeDexFiles(List allCodePaths, String[] instructionSets) { + void removeDexFiles(List allCodePaths, String[] instructionSets) { if (!allCodePaths.isEmpty()) { if (instructionSets == null) { throw new IllegalStateException("instructionSet == null"); @@ -18222,7 +18231,8 @@ public class PackageManagerService extends IPackageManager.Stub getCompilerFilterForReason(REASON_INSTALL), getOrCreateCompilerPackageStats(pkg), mDexManager.isUsedByOtherApps(pkg.packageName), - true /* bootComplete */); + true /* bootComplete */, + false /* downgrade */); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -24984,6 +24994,73 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); return mInstantAppRegistry.getInstantAppAndroidIdLPw(packageName, userId); } } + + boolean canHaveOatDir(String packageName) { + synchronized (mPackages) { + PackageParser.Package p = mPackages.get(packageName); + if (p == null) { + return false; + } + return p.canHaveOatDir(); + } + } + + private String getOatDir(PackageParser.Package pkg) { + if (!pkg.canHaveOatDir()) { + return null; + } + File codePath = new File(pkg.codePath); + if (codePath.isDirectory()) { + return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath(); + } + return null; + } + + void deleteOatArtifactsOfPackage(String packageName) { + final String[] instructionSets; + final List codePaths; + final String oatDir; + final PackageParser.Package pkg; + synchronized (mPackages) { + pkg = mPackages.get(packageName); + } + instructionSets = getAppDexInstructionSets(pkg.applicationInfo); + codePaths = pkg.getAllCodePaths(); + oatDir = getOatDir(pkg); + + for (String codePath : codePaths) { + for (String isa : instructionSets) { + try { + mInstaller.deleteOdex(codePath, isa, oatDir); + } catch (InstallerException e) { + Log.e(TAG, "Failed deleting oat files for " + codePath, e); + } + } + } + } + + Set getUnusedPackages(long downgradeTimeThresholdMillis) { + Set unusedPackages = new HashSet<>(); + long currentTimeInMillis = System.currentTimeMillis(); + synchronized (mPackages) { + for (PackageParser.Package pkg : mPackages.values()) { + PackageSetting ps = mSettings.mPackages.get(pkg.packageName); + if (ps == null) { + continue; + } + PackageDexUsage.PackageUseInfo packageUseInfo = getDexManager().getPackageUseInfo( + pkg.packageName); + if (PackageManagerServiceUtils + .isUnusedSinceTimeInMillis(ps.firstInstallTime, currentTimeInMillis, + downgradeTimeThresholdMillis, packageUseInfo, + pkg.getLatestPackageUseTimeInMills(), + pkg.getLatestForegroundPackageUseTimeInMills())) { + unusedPackages.add(pkg.packageName); + } + } + } + return unusedPackages; + } } interface PackageSender { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index ec248f5719b38..1a97a72cf2b39 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -26,7 +26,7 @@ import dalvik.system.DexFile; public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. static final String REASON_STRINGS[] = { - "first-boot", "boot", "install", "bg-dexopt", "ab-ota" + "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive" }; // Static block to ensure the strings array is of the right length. diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 9feee8c97c0dd..9211af60a0a19 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -16,12 +16,16 @@ package com.android.server.pm; +import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.PackageDexUsage; + import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.app.AppGlobals; import android.content.Intent; +import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.content.pm.ResolveInfo; import android.os.Build; @@ -178,6 +182,41 @@ public class PackageManagerServiceUtils { return result; } + /** + * Checks if the package was inactive during since thresholdTimeinMillis. + * Package is considered active, if: + * 1) It was active in foreground. + * 2) It was active in background and also used by other apps. + * + * If it doesn't have sufficient information about the package, it return false. + */ + static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis, + long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo, + long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) { + + if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) { + return false; + } + + // If the app was active in foreground during the threshold period. + boolean isActiveInForeground = (currentTimeInMillis + - latestForegroundPackageUseTimeInMillis) + < thresholdTimeinMillis; + + if (isActiveInForeground) { + return false; + } + + // If the app was active in background during the threshold period and was used + // by other packages. + boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis + - latestPackageUseTimeInMillis) + < thresholdTimeinMillis) + && packageUseInfo.isUsedByOtherApps(); + + return !isActiveInBackgroundAndUsedByOtherPackages; + } + /** * Returns the canonicalized path of {@code path} as per {@code realpath(3)} * semantics. diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index db2d30f9e5684..441994d8c590d 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -304,10 +304,11 @@ public class DexManager { * @return true if all secondary dex files were processed successfully (compiled or skipped * because they don't need to be compiled).. */ - public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) { + public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force, + boolean downgrade) { return dexoptSecondaryDex(packageName, PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason), - force, /* compileOnlySharedDex */ false); + force, /* compileOnlySharedDex */ false, downgrade); } /** @@ -316,7 +317,7 @@ public class DexManager { * because they don't need to be compiled).. */ public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force, - boolean compileOnlySharedDex) { + boolean compileOnlySharedDex, boolean downgrade) { // Select the dex optimizer based on the force parameter. // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust // the necessary dexopt flags to make sure that compilation is not skipped. This avoid @@ -360,7 +361,8 @@ public class DexManager { } int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, - dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); + dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps(), + downgrade); success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); } return success; @@ -476,7 +478,7 @@ public class DexManager { String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason( PackageManagerService.REASON_INSTALL); int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas, - compilerFilter, isUsedByOtherApps); + compilerFilter, isUsedByOtherApps, /* downgrade */ false); // If we fail to optimize the package log an error but don't propagate the error // back to the app. The app cannot do much about it and the background job