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

Commit 726959bf authored by David Brazdil's avatar David Brazdil Committed by Android (Google) Code Review
Browse files

Merge changes If7388e15,Ica73e67a,Ia4d4362e into nyc-dev

* changes:
  Assume package failed to compile unless proven otherwise
  Refactor return values of performDexOpt
  Update packages in post-boot background job
parents fecb3b8f 80932c14
Loading
Loading
Loading
Loading
+155 −34
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

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;
@@ -23,6 +25,9 @@ import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.ServiceManager;
import android.util.ArraySet;
import android.util.Log;
@@ -38,7 +43,9 @@ public class BackgroundDexOptService extends JobService {

    static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR;

    static final int BACKGROUND_DEXOPT_JOB = 800;
    static final int JOB_IDLE_OPTIMIZE = 800;
    static final int JOB_POST_BOOT_UPDATE = 801;

    private static ComponentName sDexoptServiceName = new ComponentName(
            "android",
            BackgroundDexOptService.class.getName());
@@ -48,66 +55,180 @@ public class BackgroundDexOptService extends JobService {
     */
    static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();

    final AtomicBoolean mIdleTime = new AtomicBoolean(false);
    /**
     * Atomics set to true if the JobScheduler requests an abort.
     */
    final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
    final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);

    /**
     * Atomic set to true if one job should exit early because another job was started.
     */
    final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);

    public static void schedule(Context context) {
        JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JobInfo job = new JobInfo.Builder(BACKGROUND_DEXOPT_JOB, sDexoptServiceName)

        // Schedule a one-off job which scans installed packages and updates
        // out-of-date oat files.
        js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
                    .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
                    .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
                    .build());

        // Schedule a daily job which scans installed packages and compiles
        // those with fresh profiling data.
        js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
                    .setRequiresDeviceIdle(true)
                    .setRequiresCharging(true)
                    .setPeriodic(TimeUnit.DAYS.toMillis(1))
                .build();
        js.schedule(job);
                    .build());

        if (DEBUG_DEXOPT) {
            Log.i(TAG, "Jobs scheduled");
        }
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.i(TAG, "onStartJob");
        final PackageManagerService pm =
                (PackageManagerService)ServiceManager.getService("package");
    // Returns the current battery level as a 0-100 integer.
    private int getBatteryLevel() {
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = registerReceiver(null, filter);
        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

        if (pm.isStorageLow()) {
            Log.i(TAG, "Low storage, skipping this run");
            return false;
        if (level < 0 || scale <= 0) {
            // Battery data unavailable. This should never happen, so assume the worst.
            return 0;
        }
        final ArraySet<String> pkgs = pm.getOptimizablePackages();
        if (pkgs == null || pkgs.isEmpty()) {
            Log.i(TAG, "No packages to optimize");

        return (100 * level / scale);
    }

    private boolean runPostBootUpdate(final JobParameters jobParams,
            final PackageManagerService pm, final ArraySet<String> pkgs) {
        if (mExitPostBootUpdate.get()) {
            // This job has already been superseded. Do not start it.
            return false;
        }

        final JobParameters jobParams = params;
        mIdleTime.set(true);
        new Thread("BackgroundDexOptService_DexOpter") {
        // Load low battery threshold from the system config. This is a 0-100 integer.
        final int lowBatteryThreshold = getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryWarningLevel);

        mAbortPostBootUpdate.set(false);
        new Thread("BackgroundDexOptService_PostBootUpdate") {
            @Override
            public void run() {
                for (String pkg : pkgs) {
                    if (!mIdleTime.get()) {
                        // Out of the idle state. Stop the compilation.
                    if (mAbortPostBootUpdate.get()) {
                        // JobScheduler requested an early abort.
                        return;
                    }
                    if (mExitPostBootUpdate.get()) {
                        // Different job, which supersedes this one, is running.
                        break;
                    }
                    if (getBatteryLevel() < lowBatteryThreshold) {
                        // Rather bail than completely drain the battery.
                        break;
                    }
                    if (DEBUG_DEXOPT) {
                        Log.i(TAG, "Updating package " + pkg);
                    }
                    // Update package if needed. Note that there can be no race between concurrent
                    // jobs because PackageDexOptimizer.performDexOpt is synchronized.
                    pm.performDexOpt(pkg,
                            /* instruction set */ null,
                            /* checkProfiles */ false,
                            PackageManagerService.REASON_BOOT,
                            /* force */ false);
                }
                // Ran to completion, so we abandon our timeslice and do not reschedule.
                jobFinished(jobParams, /* reschedule */ false);
            }
        }.start();
        return true;
    }

    private boolean runIdleOptimization(final JobParameters jobParams,
            final PackageManagerService pm, final ArraySet<String> pkgs) {
        // If post-boot update is still running, request that it exits early.
        mExitPostBootUpdate.set(true);

        mAbortIdleOptimization.set(false);
        new Thread("BackgroundDexOptService_IdleOptimization") {
            @Override
            public void run() {
                for (String pkg : pkgs) {
                    if (mAbortIdleOptimization.get()) {
                        // JobScheduler requested an early abort.
                        return;
                    }
                    if (sFailedPackageNames.contains(pkg)) {
                        // skip previously failing package
                        // Skip previously failing package
                        continue;
                    }
                    if (!pm.performDexOpt(pkg, /* instruction set */ null, /* checkProfiles */ true,
                            PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)) {
                        // there was a problem running dexopt,
                        // remember this so we do not keep retrying.
                    // Conservatively add package to the list of failing ones in case performDexOpt
                    // never returns.
                    sFailedPackageNames.add(pkg);
                    // Optimize package if needed. Note that there can be no race between
                    // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
                    if (pm.performDexOpt(pkg,
                            /* instruction set */ null,
                            /* checkProfiles */ true,
                            PackageManagerService.REASON_BACKGROUND_DEXOPT,
                            /* force */ false)) {
                        // Dexopt succeeded, remove package from the list of failing ones.
                        sFailedPackageNames.remove(pkg);
                    }
                }
                // ran to completion, so we abandon our timeslice and do not reschedule
                jobFinished(jobParams, false);
                // Ran to completion, so we abandon our timeslice and do not reschedule.
                jobFinished(jobParams, /* reschedule */ false);
            }
        }.start();
        return true;
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        if (DEBUG_DEXOPT) {
            Log.i(TAG, "onStartJob");
        }

        PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
        if (pm.isStorageLow()) {
            if (DEBUG_DEXOPT) {
                Log.i(TAG, "Low storage, skipping this run");
            }
            return false;
        }

        final ArraySet<String> pkgs = pm.getOptimizablePackages();
        if (pkgs == null || pkgs.isEmpty()) {
            if (DEBUG_DEXOPT) {
                Log.i(TAG, "No packages to optimize");
            }
            return false;
        }

        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
            return runPostBootUpdate(params, pm, pkgs);
        } else {
            return runIdleOptimization(params, pm, pkgs);
        }
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        Log.i(TAG, "onIdleStop");
        mIdleTime.set(false);
        if (DEBUG_DEXOPT) {
            Log.i(TAG, "onStopJob");
        }

        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
            mAbortPostBootUpdate.set(true);
        } else {
            mAbortIdleOptimization.set(true);
        }
        return false;
    }
}
+12 −6
Original line number Diff line number Diff line
@@ -53,7 +53,6 @@ class PackageDexOptimizer {
    // TODO b/19550105 Remove error codes and use exceptions
    static final int DEX_OPT_SKIPPED = 0;
    static final int DEX_OPT_PERFORMED = 1;
    static final int DEX_OPT_DEFERRED = 2;
    static final int DEX_OPT_FAILED = -1;

    private final Installer mInstaller;
@@ -170,6 +169,8 @@ class PackageDexOptimizer {
        final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;

        boolean performedDexOpt = false;
        boolean successfulDexOpt = true;

        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
            for (String path : paths) {
@@ -226,15 +227,20 @@ class PackageDexOptimizer {
                    performedDexOpt = true;
                } catch (InstallerException e) {
                    Slog.w(TAG, "Failed to dexopt", e);
                    successfulDexOpt = false;
                }
            }
        }

        // If we've gotten here, we're sure that no error occurred and that we haven't
        // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
        // we've skipped all of them because they are up to date. In both cases this
        // package doesn't need dexopt any longer.
        if (successfulDexOpt) {
            // If we've gotten here, we're sure that no error occurred. We've either
            // dex-opted one or more paths or instruction sets or we've skipped
            // all of them because they are up to date. In both cases this package
            // doesn't need dexopt any longer.
            return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
        } else {
            return DEX_OPT_FAILED;
        }
    }

    /**
+3 −1
Original line number Diff line number Diff line
@@ -7147,6 +7147,8 @@ public class PackageManagerService extends IPackageManager.Stub {
        }
    }
    // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
    // if the package can now be considered up to date for the given filter.
    private boolean performDexOptInternal(String packageName, String instructionSet,
                boolean checkProfiles, String targetCompilerFilter, boolean force) {
        PackageParser.Package p;
@@ -7167,7 +7169,7 @@ public class PackageManagerService extends IPackageManager.Stub {
                final String[] instructionSets = new String[] { targetInstructionSet };
                int result = performDexOptInternalWithDependenciesLI(p, instructionSets,
                        checkProfiles, targetCompilerFilter, force);
                return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
                return result != PackageDexOptimizer.DEX_OPT_FAILED;
            }
        } finally {
            Binder.restoreCallingIdentity(callingId);