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

Commit 37a87698 authored by David Brazdil's avatar David Brazdil
Browse files

Update packages in post-boot background job

Adds a post-boot job which scans all optimizable packages and updates
those whose OAT files are out of date. This is meant to offset the
fact that on OTA we only update the most used packages.

Bug: 27901338
Change-Id: Ia4d4362ecead1ca63d08d62c6814dad4b810f7cc
parent df839271
Loading
Loading
Loading
Loading
+151 −33
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,177 @@ 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.
                    // 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 failed, remember this so we do not keep retrying.
                        sFailedPackageNames.add(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;
    }
}