Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 246dccf9 authored by Shubham Ajmera's avatar Shubham Ajmera
Browse files

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
parent 39c9e187
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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.
+3 −2
Original line number Diff line number Diff line
@@ -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: "
+74 −12
Original line number Diff line number Diff line
@@ -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<String> pkgs, Context context) {
    private int idleOptimization(PackageManagerService pm, ArraySet<String> 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<String> failedPackageNames) {
        ArraySet<String> updatedPackages = new ArraySet<>();
        Set<String> 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,11 +299,36 @@ public class BackgroundDexOptService extends JobService {
                if (failedPackageNames.contains(pkg)) {
                    // Skip previously failing package
                    continue;
                }
            }

            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 {
                    // Conservatively add package to the list of failing ones in case performDexOpt
                    // never returns.
                    failedPackageNames.add(pkg);
                    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
@@ -297,17 +337,19 @@ public class BackgroundDexOptService extends JobService {
            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));
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -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);
        }
+10 −32
Original line number Diff line number Diff line
@@ -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;
    }

Loading