blob: cec105816b36b43f56f590ea573545b0edbce5de [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 {
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070045 static final String TAG = "BackgroundDexOptService";
46
Christopher Tate2c9655b2015-06-12 13:06:45 -070047 static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR;
48
David Brazdil37a87692016-04-07 10:43:18 +010049 static final int JOB_IDLE_OPTIMIZE = 800;
50 static final int JOB_POST_BOOT_UPDATE = 801;
51
Christopher Tatecf1a2f72014-06-16 15:51:39 -070052 private static ComponentName sDexoptServiceName = new ComponentName(
Christopher Tate1b8b3aa2014-06-20 13:17:01 -070053 "android",
Christopher Tatecf1a2f72014-06-16 15:51:39 -070054 BackgroundDexOptService.class.getName());
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070055
Brian Carlstroma00be9b2014-12-12 13:14:36 -080056 /**
57 * Set of failed packages remembered across job runs.
58 */
59 static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
60
David Brazdil37a87692016-04-07 10:43:18 +010061 /**
62 * Atomics set to true if the JobScheduler requests an abort.
63 */
64 final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
65 final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
66
67 /**
68 * Atomic set to true if one job should exit early because another job was started.
69 */
70 final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070071
Narayan Kamath8735f072016-08-16 18:25:08 +010072 private final File dataDir = Environment.getDataDirectory();
73
Calin Juravledb4a79a2015-12-23 18:55:08 +020074 public static void schedule(Context context) {
Christopher Tatecf1a2f72014-06-16 15:51:39 -070075 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
David Brazdil37a87692016-04-07 10:43:18 +010076
77 // Schedule a one-off job which scans installed packages and updates
78 // out-of-date oat files.
79 js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
80 .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
81 .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
82 .build());
83
84 // Schedule a daily job which scans installed packages and compiles
85 // those with fresh profiling data.
86 js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
87 .setRequiresDeviceIdle(true)
88 .setRequiresCharging(true)
89 .setPeriodic(TimeUnit.DAYS.toMillis(1))
90 .build());
91
92 if (DEBUG_DEXOPT) {
93 Log.i(TAG, "Jobs scheduled");
94 }
Brian Carlstrom7395a8a2014-04-28 22:11:01 -070095 }
96
David Brazdilace80c52016-04-11 13:37:29 +010097 public static void notifyPackageChanged(String packageName) {
98 // The idle maintanance job skips packages which previously failed to
99 // compile. The given package has changed and may successfully compile
100 // now. Remove it from the list of known failing packages.
101 synchronized (sFailedPackageNames) {
102 sFailedPackageNames.remove(packageName);
103 }
104 }
105
David Brazdil37a87692016-04-07 10:43:18 +0100106 // Returns the current battery level as a 0-100 integer.
107 private int getBatteryLevel() {
108 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
109 Intent intent = registerReceiver(null, filter);
110 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
111 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700112
David Brazdil37a87692016-04-07 10:43:18 +0100113 if (level < 0 || scale <= 0) {
114 // Battery data unavailable. This should never happen, so assume the worst.
115 return 0;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700116 }
David Brazdil37a87692016-04-07 10:43:18 +0100117
118 return (100 * level / scale);
119 }
120
Narayan Kamath8735f072016-08-16 18:25:08 +0100121 private long getLowStorageThreshold() {
122 @SuppressWarnings("deprecation")
123 final long lowThreshold = StorageManager.from(this).getStorageLowBytes(dataDir);
124 if (lowThreshold == 0) {
125 Log.e(TAG, "Invalid low storage threshold");
126 }
127
128 return lowThreshold;
129 }
130
David Brazdil37a87692016-04-07 10:43:18 +0100131 private boolean runPostBootUpdate(final JobParameters jobParams,
132 final PackageManagerService pm, final ArraySet<String> pkgs) {
133 if (mExitPostBootUpdate.get()) {
134 // This job has already been superseded. Do not start it.
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700135 return false;
136 }
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700137
David Brazdil37a87692016-04-07 10:43:18 +0100138 // Load low battery threshold from the system config. This is a 0-100 integer.
139 final int lowBatteryThreshold = getResources().getInteger(
140 com.android.internal.R.integer.config_lowBatteryWarningLevel);
141
Narayan Kamath8735f072016-08-16 18:25:08 +0100142 final long lowThreshold = getLowStorageThreshold();
143
David Brazdil37a87692016-04-07 10:43:18 +0100144 mAbortPostBootUpdate.set(false);
145 new Thread("BackgroundDexOptService_PostBootUpdate") {
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700146 @Override
147 public void run() {
148 for (String pkg : pkgs) {
David Brazdil37a87692016-04-07 10:43:18 +0100149 if (mAbortPostBootUpdate.get()) {
150 // JobScheduler requested an early abort.
151 return;
152 }
153 if (mExitPostBootUpdate.get()) {
154 // Different job, which supersedes this one, is running.
155 break;
156 }
157 if (getBatteryLevel() < lowBatteryThreshold) {
158 // Rather bail than completely drain the battery.
159 break;
160 }
Narayan Kamath8735f072016-08-16 18:25:08 +0100161 long usableSpace = dataDir.getUsableSpace();
162 if (usableSpace < lowThreshold) {
163 // Rather bail than completely fill up the disk.
164 Log.w(TAG, "Aborting background dex opt job due to low storage: " +
165 usableSpace);
166 break;
167 }
168
David Brazdil37a87692016-04-07 10:43:18 +0100169 if (DEBUG_DEXOPT) {
170 Log.i(TAG, "Updating package " + pkg);
171 }
Calin Juravle8412cf42016-05-09 17:56:18 +0100172
David Brazdil37a87692016-04-07 10:43:18 +0100173 // Update package if needed. Note that there can be no race between concurrent
174 // jobs because PackageDexOptimizer.performDexOpt is synchronized.
Calin Juravle8412cf42016-05-09 17:56:18 +0100175
176 // checkProfiles is false to avoid merging profiles during boot which
177 // might interfere with background compilation (b/28612421).
178 // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
179 // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
180 // trade-off worth doing to save boot time work.
David Brazdil37a87692016-04-07 10:43:18 +0100181 pm.performDexOpt(pkg,
Calin Juravle8412cf42016-05-09 17:56:18 +0100182 /* checkProfiles */ false,
David Brazdil37a87692016-04-07 10:43:18 +0100183 PackageManagerService.REASON_BOOT,
184 /* force */ false);
185 }
186 // Ran to completion, so we abandon our timeslice and do not reschedule.
187 jobFinished(jobParams, /* reschedule */ false);
188 }
189 }.start();
190 return true;
191 }
192
193 private boolean runIdleOptimization(final JobParameters jobParams,
194 final PackageManagerService pm, final ArraySet<String> pkgs) {
195 // If post-boot update is still running, request that it exits early.
196 mExitPostBootUpdate.set(true);
197
198 mAbortIdleOptimization.set(false);
Narayan Kamath8735f072016-08-16 18:25:08 +0100199
200 final long lowThreshold = getLowStorageThreshold();
201
David Brazdil37a87692016-04-07 10:43:18 +0100202 new Thread("BackgroundDexOptService_IdleOptimization") {
203 @Override
204 public void run() {
205 for (String pkg : pkgs) {
206 if (mAbortIdleOptimization.get()) {
207 // JobScheduler requested an early abort.
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700208 return;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700209 }
Brian Carlstroma00be9b2014-12-12 13:14:36 -0800210 if (sFailedPackageNames.contains(pkg)) {
David Brazdil37a87692016-04-07 10:43:18 +0100211 // Skip previously failing package
Brian Carlstroma00be9b2014-12-12 13:14:36 -0800212 continue;
213 }
Narayan Kamath8735f072016-08-16 18:25:08 +0100214
215 long usableSpace = dataDir.getUsableSpace();
216 if (usableSpace < lowThreshold) {
217 // Rather bail than completely fill up the disk.
218 Log.w(TAG, "Aborting background dex opt job due to low storage: " +
219 usableSpace);
220 break;
221 }
222
David Brazdil80932c12016-04-08 16:38:04 +0100223 // Conservatively add package to the list of failing ones in case performDexOpt
224 // never returns.
David Brazdilace80c52016-04-11 13:37:29 +0100225 synchronized (sFailedPackageNames) {
226 sFailedPackageNames.add(pkg);
227 }
David Brazdil37a87692016-04-07 10:43:18 +0100228 // Optimize package if needed. Note that there can be no race between
229 // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
David Brazdil80932c12016-04-08 16:38:04 +0100230 if (pm.performDexOpt(pkg,
David Brazdil37a87692016-04-07 10:43:18 +0100231 /* checkProfiles */ true,
232 PackageManagerService.REASON_BACKGROUND_DEXOPT,
233 /* force */ false)) {
David Brazdil80932c12016-04-08 16:38:04 +0100234 // Dexopt succeeded, remove package from the list of failing ones.
David Brazdilace80c52016-04-11 13:37:29 +0100235 synchronized (sFailedPackageNames) {
236 sFailedPackageNames.remove(pkg);
237 }
Brian Carlstroma00be9b2014-12-12 13:14:36 -0800238 }
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700239 }
David Brazdil37a87692016-04-07 10:43:18 +0100240 // Ran to completion, so we abandon our timeslice and do not reschedule.
241 jobFinished(jobParams, /* reschedule */ false);
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700242 }
243 }.start();
244 return true;
245 }
246
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700247 @Override
David Brazdil37a87692016-04-07 10:43:18 +0100248 public boolean onStartJob(JobParameters params) {
249 if (DEBUG_DEXOPT) {
250 Log.i(TAG, "onStartJob");
251 }
252
Narayan Kamath8735f072016-08-16 18:25:08 +0100253 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
254 // the checks above. This check is not "live" - the value is determined by a background
255 // restart with a period of ~1 minute.
David Brazdil37a87692016-04-07 10:43:18 +0100256 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
257 if (pm.isStorageLow()) {
258 if (DEBUG_DEXOPT) {
259 Log.i(TAG, "Low storage, skipping this run");
260 }
261 return false;
262 }
263
264 final ArraySet<String> pkgs = pm.getOptimizablePackages();
265 if (pkgs == null || pkgs.isEmpty()) {
266 if (DEBUG_DEXOPT) {
267 Log.i(TAG, "No packages to optimize");
268 }
269 return false;
270 }
271
272 if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
273 return runPostBootUpdate(params, pm, pkgs);
274 } else {
275 return runIdleOptimization(params, pm, pkgs);
276 }
277 }
278
279 @Override
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700280 public boolean onStopJob(JobParameters params) {
David Brazdil37a87692016-04-07 10:43:18 +0100281 if (DEBUG_DEXOPT) {
282 Log.i(TAG, "onStopJob");
283 }
284
285 if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
286 mAbortPostBootUpdate.set(true);
287 } else {
288 mAbortIdleOptimization.set(true);
289 }
Christopher Tatecf1a2f72014-06-16 15:51:39 -0700290 return false;
Brian Carlstrom7395a8a2014-04-28 22:11:01 -0700291 }
292}