From 37a87698f5b13e8d24475cb05d48a3da7339192c Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Thu, 7 Apr 2016 10:43:18 +0100 Subject: [PATCH] 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 --- .../server/pm/BackgroundDexOptService.java | 184 ++++++++++++++---- 1 file changed, 151 insertions(+), 33 deletions(-) diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index eae2eaaaecad7..2ce2dfe407f57 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -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 sFailedPackageNames = new ArraySet(); - 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) - .setRequiresDeviceIdle(true) - .setRequiresCharging(true) - .setPeriodic(TimeUnit.DAYS.toMillis(1)) - .build(); - js.schedule(job); + + // 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()); + + 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 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 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 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 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; } }