• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.pm;
18 
19 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
20 
21 import android.app.AlarmManager;
22 import android.app.job.JobInfo;
23 import android.app.job.JobParameters;
24 import android.app.job.JobScheduler;
25 import android.app.job.JobService;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.os.BatteryManager;
31 import android.os.ServiceManager;
32 import android.util.ArraySet;
33 import android.util.Log;
34 
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * {@hide}
40  */
41 public 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 
schedule(Context context)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 
notifyPackageChanged(String packageName)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.
getBatteryLevel()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 
runPostBootUpdate(final JobParameters jobParams, final PackageManagerService pm, final ArraySet<String> pkgs)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                             /* checkProfiles */ false,
158                             PackageManagerService.REASON_BOOT,
159                             /* force */ false);
160                 }
161                 // Ran to completion, so we abandon our timeslice and do not reschedule.
162                 jobFinished(jobParams, /* reschedule */ false);
163             }
164         }.start();
165         return true;
166     }
167 
runIdleOptimization(final JobParameters jobParams, final PackageManagerService pm, final ArraySet<String> pkgs)168     private boolean runIdleOptimization(final JobParameters jobParams,
169             final PackageManagerService pm, final ArraySet<String> pkgs) {
170         // If post-boot update is still running, request that it exits early.
171         mExitPostBootUpdate.set(true);
172 
173         mAbortIdleOptimization.set(false);
174         new Thread("BackgroundDexOptService_IdleOptimization") {
175             @Override
176             public void run() {
177                 for (String pkg : pkgs) {
178                     if (mAbortIdleOptimization.get()) {
179                         // JobScheduler requested an early abort.
180                         return;
181                     }
182                     if (sFailedPackageNames.contains(pkg)) {
183                         // Skip previously failing package
184                         continue;
185                     }
186                     // Conservatively add package to the list of failing ones in case performDexOpt
187                     // never returns.
188                     synchronized (sFailedPackageNames) {
189                         sFailedPackageNames.add(pkg);
190                     }
191                     // Optimize package if needed. Note that there can be no race between
192                     // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
193                     if (pm.performDexOpt(pkg,
194                             /* checkProfiles */ true,
195                             PackageManagerService.REASON_BACKGROUND_DEXOPT,
196                             /* force */ false)) {
197                         // Dexopt succeeded, remove package from the list of failing ones.
198                         synchronized (sFailedPackageNames) {
199                             sFailedPackageNames.remove(pkg);
200                         }
201                     }
202                 }
203                 // Ran to completion, so we abandon our timeslice and do not reschedule.
204                 jobFinished(jobParams, /* reschedule */ false);
205             }
206         }.start();
207         return true;
208     }
209 
210     @Override
onStartJob(JobParameters params)211     public boolean onStartJob(JobParameters params) {
212         if (DEBUG_DEXOPT) {
213             Log.i(TAG, "onStartJob");
214         }
215 
216         PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
217         if (pm.isStorageLow()) {
218             if (DEBUG_DEXOPT) {
219                 Log.i(TAG, "Low storage, skipping this run");
220             }
221             return false;
222         }
223 
224         final ArraySet<String> pkgs = pm.getOptimizablePackages();
225         if (pkgs == null || pkgs.isEmpty()) {
226             if (DEBUG_DEXOPT) {
227                 Log.i(TAG, "No packages to optimize");
228             }
229             return false;
230         }
231 
232         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
233             return runPostBootUpdate(params, pm, pkgs);
234         } else {
235             return runIdleOptimization(params, pm, pkgs);
236         }
237     }
238 
239     @Override
onStopJob(JobParameters params)240     public boolean onStopJob(JobParameters params) {
241         if (DEBUG_DEXOPT) {
242             Log.i(TAG, "onStopJob");
243         }
244 
245         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
246             mAbortPostBootUpdate.set(true);
247         } else {
248             mAbortIdleOptimization.set(true);
249         }
250         return false;
251     }
252 }
253