BackgroundDexOptService.java revision 8412cf4daaa437003f4a79a82aa35465c4f0d418
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.pm;
18
19import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
20
21import android.app.AlarmManager;
22import android.app.job.JobInfo;
23import android.app.job.JobParameters;
24import android.app.job.JobScheduler;
25import android.app.job.JobService;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.os.BatteryManager;
31import android.os.ServiceManager;
32import android.util.ArraySet;
33import android.util.Log;
34
35import java.util.concurrent.atomic.AtomicBoolean;
36import java.util.concurrent.TimeUnit;
37
38/**
39 * {@hide}
40 */
41public class BackgroundDexOptService extends JobService {
42    static final String TAG = "BackgroundDexOptService";
43
44    static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR;
45
46    static final int JOB_IDLE_OPTIMIZE = 800;
47    static final int JOB_POST_BOOT_UPDATE = 801;
48
49    private static ComponentName sDexoptServiceName = new ComponentName(
50            "android",
51            BackgroundDexOptService.class.getName());
52
53    /**
54     * Set of failed packages remembered across job runs.
55     */
56    static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
57
58    /**
59     * Atomics set to true if the JobScheduler requests an abort.
60     */
61    final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
62    final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
63
64    /**
65     * Atomic set to true if one job should exit early because another job was started.
66     */
67    final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
68
69    public static void schedule(Context context) {
70        JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
71
72        // Schedule a one-off job which scans installed packages and updates
73        // out-of-date oat files.
74        js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
75                    .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
76                    .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
77                    .build());
78
79        // Schedule a daily job which scans installed packages and compiles
80        // those with fresh profiling data.
81        js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
82                    .setRequiresDeviceIdle(true)
83                    .setRequiresCharging(true)
84                    .setPeriodic(TimeUnit.DAYS.toMillis(1))
85                    .build());
86
87        if (DEBUG_DEXOPT) {
88            Log.i(TAG, "Jobs scheduled");
89        }
90    }
91
92    public static void notifyPackageChanged(String packageName) {
93        // The idle maintanance job skips packages which previously failed to
94        // compile. The given package has changed and may successfully compile
95        // now. Remove it from the list of known failing packages.
96        synchronized (sFailedPackageNames) {
97            sFailedPackageNames.remove(packageName);
98        }
99    }
100
101    // Returns the current battery level as a 0-100 integer.
102    private int getBatteryLevel() {
103        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
104        Intent intent = registerReceiver(null, filter);
105        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
106        int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
107
108        if (level < 0 || scale <= 0) {
109            // Battery data unavailable. This should never happen, so assume the worst.
110            return 0;
111        }
112
113        return (100 * level / scale);
114    }
115
116    private boolean runPostBootUpdate(final JobParameters jobParams,
117            final PackageManagerService pm, final ArraySet<String> pkgs) {
118        if (mExitPostBootUpdate.get()) {
119            // This job has already been superseded. Do not start it.
120            return false;
121        }
122
123        // Load low battery threshold from the system config. This is a 0-100 integer.
124        final int lowBatteryThreshold = getResources().getInteger(
125                com.android.internal.R.integer.config_lowBatteryWarningLevel);
126
127        mAbortPostBootUpdate.set(false);
128        new Thread("BackgroundDexOptService_PostBootUpdate") {
129            @Override
130            public void run() {
131                for (String pkg : pkgs) {
132                    if (mAbortPostBootUpdate.get()) {
133                        // JobScheduler requested an early abort.
134                        return;
135                    }
136                    if (mExitPostBootUpdate.get()) {
137                        // Different job, which supersedes this one, is running.
138                        break;
139                    }
140                    if (getBatteryLevel() < lowBatteryThreshold) {
141                        // Rather bail than completely drain the battery.
142                        break;
143                    }
144                    if (DEBUG_DEXOPT) {
145                        Log.i(TAG, "Updating package " + pkg);
146                    }
147
148                    // Update package if needed. Note that there can be no race between concurrent
149                    // jobs because PackageDexOptimizer.performDexOpt is synchronized.
150
151                    // checkProfiles is false to avoid merging profiles during boot which
152                    // might interfere with background compilation (b/28612421).
153                    // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
154                    // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
155                    // trade-off worth doing to save boot time work.
156                    pm.performDexOpt(pkg,
157                            /* instruction set */ null,
158                            /* checkProfiles */ false,
159                            PackageManagerService.REASON_BOOT,
160                            /* force */ false);
161                }
162                // Ran to completion, so we abandon our timeslice and do not reschedule.
163                jobFinished(jobParams, /* reschedule */ false);
164            }
165        }.start();
166        return true;
167    }
168
169    private boolean runIdleOptimization(final JobParameters jobParams,
170            final PackageManagerService pm, final ArraySet<String> pkgs) {
171        // If post-boot update is still running, request that it exits early.
172        mExitPostBootUpdate.set(true);
173
174        mAbortIdleOptimization.set(false);
175        new Thread("BackgroundDexOptService_IdleOptimization") {
176            @Override
177            public void run() {
178                for (String pkg : pkgs) {
179                    if (mAbortIdleOptimization.get()) {
180                        // JobScheduler requested an early abort.
181                        return;
182                    }
183                    if (sFailedPackageNames.contains(pkg)) {
184                        // Skip previously failing package
185                        continue;
186                    }
187                    // Conservatively add package to the list of failing ones in case performDexOpt
188                    // never returns.
189                    synchronized (sFailedPackageNames) {
190                        sFailedPackageNames.add(pkg);
191                    }
192                    // Optimize package if needed. Note that there can be no race between
193                    // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
194                    if (pm.performDexOpt(pkg,
195                            /* instruction set */ null,
196                            /* checkProfiles */ true,
197                            PackageManagerService.REASON_BACKGROUND_DEXOPT,
198                            /* force */ false)) {
199                        // Dexopt succeeded, remove package from the list of failing ones.
200                        synchronized (sFailedPackageNames) {
201                            sFailedPackageNames.remove(pkg);
202                        }
203                    }
204                }
205                // Ran to completion, so we abandon our timeslice and do not reschedule.
206                jobFinished(jobParams, /* reschedule */ false);
207            }
208        }.start();
209        return true;
210    }
211
212    @Override
213    public boolean onStartJob(JobParameters params) {
214        if (DEBUG_DEXOPT) {
215            Log.i(TAG, "onStartJob");
216        }
217
218        PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
219        if (pm.isStorageLow()) {
220            if (DEBUG_DEXOPT) {
221                Log.i(TAG, "Low storage, skipping this run");
222            }
223            return false;
224        }
225
226        final ArraySet<String> pkgs = pm.getOptimizablePackages();
227        if (pkgs == null || pkgs.isEmpty()) {
228            if (DEBUG_DEXOPT) {
229                Log.i(TAG, "No packages to optimize");
230            }
231            return false;
232        }
233
234        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
235            return runPostBootUpdate(params, pm, pkgs);
236        } else {
237            return runIdleOptimization(params, pm, pkgs);
238        }
239    }
240
241    @Override
242    public boolean onStopJob(JobParameters params) {
243        if (DEBUG_DEXOPT) {
244            Log.i(TAG, "onStopJob");
245        }
246
247        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
248            mAbortPostBootUpdate.set(true);
249        } else {
250            mAbortIdleOptimization.set(true);
251        }
252        return false;
253    }
254}
255