blob: 601a2194e8f37992d8ffc116a46fd3a1020d8901 [file] [log] [blame]
Brian Carlstrom7395a8a2014-04-28 22:11:01 -07001/*
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
David Brazdil37a87692016-04-07 10:43:18 +010019import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
20
Christopher Tate2c9655b2015-06-12 13:06:45 -070021import android.app.AlarmManager;
Christopher Tatecf1a2f72014-06-16 15:51:39 -070022import android.app.job.JobInfo;
23import android.app.job.JobParameters;
24import android.app.job.JobScheduler;
25import android.app.job.JobService;
26import android.content.ComponentName;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070027import android.content.Context;
David Brazdil37a87692016-04-07 10:43:18 +010028import android.content.Intent;
29import android.content.IntentFilter;
30import android.os.BatteryManager;
Narayan Kamath8735f072016-08-16 18:25:08 +010031import android.os.Environment;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070032import android.os.ServiceManager;
Narayan Kamath8735f072016-08-16 18:25:08 +010033import android.os.storage.StorageManager;
Jeff Sharkey9f837a92014-10-24 12:07:24 -070034import android.util.ArraySet;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070035import android.util.Log;
36
Narayan Kamath8735f072016-08-16 18:25:08 +010037import java.io.File;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070038import java.util.concurrent.atomic.AtomicBoolean;
Nicolas Geoffray27c07372015-11-05 16:54:09 +000039import java.util.concurrent.TimeUnit;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070040
41/**
42 * {@hide}
43 */
Christopher Tatecf1a2f72014-06-16 15:51:39 -070044public class BackgroundDexOptService extends JobService {
Calin Juravle3a2b7f72016-12-22 18:50:05 +020045 private static final String TAG = "BackgroundDexOptService";
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070046
Calin Juravle3a2b7f72016-12-22 18:50:05 +020047 private static final boolean DEBUG = false;
Christopher Tate2c9655b2015-06-12 13:06:45 -070048
Calin Juravle3a2b7f72016-12-22 18:50:05 +020049 private static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR;
50
51 private static final int JOB_IDLE_OPTIMIZE = 800;
52 private static final int JOB_POST_BOOT_UPDATE = 801;
53
54 private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG
55 ? TimeUnit.MINUTES.toMillis(1)
56 : TimeUnit.DAYS.toMillis(1);
David Brazdil37a87692016-04-07 10:43:18 +010057
Christopher Tatecf1a2f72014-06-16 15:51:39 -070058 private static ComponentName sDexoptServiceName = new ComponentName(
Christopher Tate1b8b3aa2014-06-20 13:17:01 -070059 "android",
Christopher Tatecf1a2f72014-06-16 15:51:39 -070060 BackgroundDexOptService.class.getName());
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070061
Brian Carlstroma00be9b2014-12-12 13:14:36 -080062 /**
63 * Set of failed packages remembered across job runs.
64 */
65 static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
66
David Brazdil37a87692016-04-07 10:43:18 +010067 /**
68 * Atomics set to true if the JobScheduler requests an abort.
69 */
70 final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
71 final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
72
73 /**
74 * Atomic set to true if one job should exit early because another job was started.
75 */
76 final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070077
Calin Juravle95176bb2016-12-22 18:50:05 +020078 private final File mDataDir = Environment.getDataDirectory();
Narayan Kamath8735f072016-08-16 18:25:08 +010079
Calin Juravledb4a79a2015-12-23 18:55:08 +020080 public static void schedule(Context context) {
Christopher Tatecf1a2f72014-06-16 15:51:39 -070081 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
David Brazdil37a87692016-04-07 10:43:18 +010082
83 // Schedule a one-off job which scans installed packages and updates
84 // out-of-date oat files.
85 js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
86 .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
87 .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
88 .build());
89
90 // Schedule a daily job which scans installed packages and compiles
91 // those with fresh profiling data.
92 js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
93 .setRequiresDeviceIdle(true)
94 .setRequiresCharging(true)
Calin Juravle3a2b7f72016-12-22 18:50:05 +020095 .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
David Brazdil37a87692016-04-07 10:43:18 +010096 .build());
97
98 if (DEBUG_DEXOPT) {
99 Log.i(TAG, "Jobs scheduled");
100 }
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700101 }
102
David Brazdilace80c52016-04-11 13:37:29 +0100103 public static void notifyPackageChanged(String packageName) {
104 // The idle maintanance job skips packages which previously failed to
105 // compile. The given package has changed and may successfully compile
106 // now. Remove it from the list of known failing packages.
107 synchronized (sFailedPackageNames) {
108 sFailedPackageNames.remove(packageName);
109 }
110 }
111
David Brazdil37a87692016-04-07 10:43:18 +0100112 // Returns the current battery level as a 0-100 integer.
113 private int getBatteryLevel() {
114 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
115 Intent intent = registerReceiver(null, filter);
116 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
117 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700118
David Brazdil37a87692016-04-07 10:43:18 +0100119 if (level < 0 || scale <= 0) {
120 // Battery data unavailable. This should never happen, so assume the worst.
121 return 0;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700122 }
David Brazdil37a87692016-04-07 10:43:18 +0100123
124 return (100 * level / scale);
125 }
126
Narayan Kamath8735f072016-08-16 18:25:08 +0100127 private long getLowStorageThreshold() {
128 @SuppressWarnings("deprecation")
Calin Juravle95176bb2016-12-22 18:50:05 +0200129 final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir);
Narayan Kamath8735f072016-08-16 18:25:08 +0100130 if (lowThreshold == 0) {
131 Log.e(TAG, "Invalid low storage threshold");
132 }
133
134 return lowThreshold;
135 }
136
David Brazdil37a87692016-04-07 10:43:18 +0100137 private boolean runPostBootUpdate(final JobParameters jobParams,
138 final PackageManagerService pm, final ArraySet<String> pkgs) {
139 if (mExitPostBootUpdate.get()) {
140 // This job has already been superseded. Do not start it.
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700141 return false;
142 }
David Brazdil37a87692016-04-07 10:43:18 +0100143 new Thread("BackgroundDexOptService_PostBootUpdate") {
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700144 @Override
145 public void run() {
Calin Juravle95176bb2016-12-22 18:50:05 +0200146 postBootUpdate(jobParams, pm, pkgs);
147 }
Narayan Kamath8735f072016-08-16 18:25:08 +0100148
Calin Juravle95176bb2016-12-22 18:50:05 +0200149 }.start();
150 return true;
151 }
Calin Juravle8412cf42016-05-09 17:56:18 +0100152
Calin Juravle95176bb2016-12-22 18:50:05 +0200153 private void postBootUpdate(JobParameters jobParams, PackageManagerService pm,
154 ArraySet<String> pkgs) {
155 // Load low battery threshold from the system config. This is a 0-100 integer.
156 final int lowBatteryThreshold = getResources().getInteger(
157 com.android.internal.R.integer.config_lowBatteryWarningLevel);
158 final long lowThreshold = getLowStorageThreshold();
Calin Juravle8412cf42016-05-09 17:56:18 +0100159
Calin Juravle95176bb2016-12-22 18:50:05 +0200160 mAbortPostBootUpdate.set(false);
161
162 for (String pkg : pkgs) {
163 if (mAbortPostBootUpdate.get()) {
164 // JobScheduler requested an early abort.
165 return;
166 }
167 if (mExitPostBootUpdate.get()) {
168 // Different job, which supersedes this one, is running.
169 break;
170 }
171 if (getBatteryLevel() < lowBatteryThreshold) {
172 // Rather bail than completely drain the battery.
173 break;
174 }
175 long usableSpace = mDataDir.getUsableSpace();
176 if (usableSpace < lowThreshold) {
177 // Rather bail than completely fill up the disk.
178 Log.w(TAG, "Aborting background dex opt job due to low storage: " +
179 usableSpace);
180 break;
181 }
182
183 if (DEBUG_DEXOPT) {
184 Log.i(TAG, "Updating package " + pkg);
185 }
186
187 // Update package if needed. Note that there can be no race between concurrent
188 // jobs because PackageDexOptimizer.performDexOpt is synchronized.
189
190 // checkProfiles is false to avoid merging profiles during boot which
191 // might interfere with background compilation (b/28612421).
192 // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
193 // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
194 // trade-off worth doing to save boot time work.
195 pm.performDexOpt(pkg,
196 /* checkProfiles */ false,
197 PackageManagerService.REASON_BOOT,
198 /* force */ false);
199 }
200 // Ran to completion, so we abandon our timeslice and do not reschedule.
201 jobFinished(jobParams, /* reschedule */ false);
202 }
203
204 private boolean runIdleOptimization(final JobParameters jobParams,
205 final PackageManagerService pm, final ArraySet<String> pkgs) {
206 new Thread("BackgroundDexOptService_IdleOptimization") {
207 @Override
208 public void run() {
209 idleOptimization(jobParams, pm, pkgs);
David Brazdil37a87692016-04-07 10:43:18 +0100210 }
211 }.start();
212 return true;
213 }
214
Calin Juravle95176bb2016-12-22 18:50:05 +0200215 private void idleOptimization(JobParameters jobParams, PackageManagerService pm,
216 ArraySet<String> pkgs) {
Calin Juravle3a2b7f72016-12-22 18:50:05 +0200217 Log.i(TAG, "Performing idle optimizations");
David Brazdil37a87692016-04-07 10:43:18 +0100218 // If post-boot update is still running, request that it exits early.
219 mExitPostBootUpdate.set(true);
220
221 mAbortIdleOptimization.set(false);
Narayan Kamath8735f072016-08-16 18:25:08 +0100222
223 final long lowThreshold = getLowStorageThreshold();
Calin Juravle95176bb2016-12-22 18:50:05 +0200224 for (String pkg : pkgs) {
225 if (mAbortIdleOptimization.get()) {
226 // JobScheduler requested an early abort.
227 return;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700228 }
Calin Juravle95176bb2016-12-22 18:50:05 +0200229
230 synchronized (sFailedPackageNames) {
231 if (sFailedPackageNames.contains(pkg)) {
232 // Skip previously failing package
233 continue;
234 }
235 }
236
237 long usableSpace = mDataDir.getUsableSpace();
238 if (usableSpace < lowThreshold) {
239 // Rather bail than completely fill up the disk.
240 Log.w(TAG, "Aborting background dex opt job due to low storage: " +
241 usableSpace);
242 break;
243 }
244
245 // Conservatively add package to the list of failing ones in case performDexOpt
246 // never returns.
247 synchronized (sFailedPackageNames) {
248 sFailedPackageNames.add(pkg);
249 }
250 // Optimize package if needed. Note that there can be no race between
251 // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
252 if (pm.performDexOpt(pkg,
253 /* checkProfiles */ true,
254 PackageManagerService.REASON_BACKGROUND_DEXOPT,
255 /* force */ false)) {
256 // Dexopt succeeded, remove package from the list of failing ones.
257 synchronized (sFailedPackageNames) {
258 sFailedPackageNames.remove(pkg);
259 }
260 }
261 }
262 // Ran to completion, so we abandon our timeslice and do not reschedule.
263 jobFinished(jobParams, /* reschedule */ false);
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700264 }
265
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700266 @Override
David Brazdil37a87692016-04-07 10:43:18 +0100267 public boolean onStartJob(JobParameters params) {
268 if (DEBUG_DEXOPT) {
269 Log.i(TAG, "onStartJob");
270 }
271
Narayan Kamath8735f072016-08-16 18:25:08 +0100272 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
273 // the checks above. This check is not "live" - the value is determined by a background
274 // restart with a period of ~1 minute.
David Brazdil37a87692016-04-07 10:43:18 +0100275 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
276 if (pm.isStorageLow()) {
277 if (DEBUG_DEXOPT) {
278 Log.i(TAG, "Low storage, skipping this run");
279 }
280 return false;
281 }
282
283 final ArraySet<String> pkgs = pm.getOptimizablePackages();
284 if (pkgs == null || pkgs.isEmpty()) {
285 if (DEBUG_DEXOPT) {
286 Log.i(TAG, "No packages to optimize");
287 }
288 return false;
289 }
290
291 if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
292 return runPostBootUpdate(params, pm, pkgs);
293 } else {
294 return runIdleOptimization(params, pm, pkgs);
295 }
296 }
297
298 @Override
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700299 public boolean onStopJob(JobParameters params) {
David Brazdil37a87692016-04-07 10:43:18 +0100300 if (DEBUG_DEXOPT) {
301 Log.i(TAG, "onStopJob");
302 }
303
304 if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
305 mAbortPostBootUpdate.set(true);
306 } else {
307 mAbortIdleOptimization.set(true);
308 }
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700309 return false;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700310 }
311}