• 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.annotation.Nullable;
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.content.pm.PackageInfo;
31 import android.os.BatteryManager;
32 import android.os.Environment;
33 import android.os.ServiceManager;
34 import android.os.SystemProperties;
35 import android.os.UserHandle;
36 import android.os.storage.StorageManager;
37 import android.util.ArraySet;
38 import android.util.Log;
39 import android.util.StatsLog;
40 
41 import com.android.internal.util.ArrayUtils;
42 import com.android.server.LocalServices;
43 import com.android.server.PinnerService;
44 import com.android.server.pm.dex.DexManager;
45 import com.android.server.pm.dex.DexoptOptions;
46 
47 import java.io.File;
48 import java.nio.file.Paths;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.concurrent.TimeUnit;
52 import java.util.concurrent.atomic.AtomicBoolean;
53 import java.util.function.Supplier;
54 
55 /**
56  * {@hide}
57  */
58 public class BackgroundDexOptService extends JobService {
59     private static final String TAG = "BackgroundDexOptService";
60 
61     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
62 
63     private static final int JOB_IDLE_OPTIMIZE = 800;
64     private static final int JOB_POST_BOOT_UPDATE = 801;
65 
66     private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG
67             ? TimeUnit.MINUTES.toMillis(1)
68             : TimeUnit.DAYS.toMillis(1);
69 
70     private static ComponentName sDexoptServiceName = new ComponentName(
71             "android",
72             BackgroundDexOptService.class.getName());
73 
74     // Possible return codes of individual optimization steps.
75 
76     // Optimizations finished. All packages were processed.
77     private static final int OPTIMIZE_PROCESSED = 0;
78     // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
79     private static final int OPTIMIZE_CONTINUE = 1;
80     // Optimizations should be aborted. Job scheduler requested it.
81     private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
82     // Optimizations should be aborted. No space left on device.
83     private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
84 
85     // Used for calculating space threshold for downgrading unused apps.
86     private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
87 
88     /**
89      * Set of failed packages remembered across job runs.
90      */
91     static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
92     static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
93 
94     /**
95      * Atomics set to true if the JobScheduler requests an abort.
96      */
97     private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
98     private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
99 
100     /**
101      * Atomic set to true if one job should exit early because another job was started.
102      */
103     private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
104 
105     private final File mDataDir = Environment.getDataDirectory();
106     private static final long mDowngradeUnusedAppsThresholdInMillis =
107             getDowngradeUnusedAppsThresholdInMillis();
108 
schedule(Context context)109     public static void schedule(Context context) {
110         if (isBackgroundDexoptDisabled()) {
111             return;
112         }
113 
114         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
115 
116         // Schedule a one-off job which scans installed packages and updates
117         // out-of-date oat files.
118         js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
119                     .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
120                     .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
121                     .build());
122 
123         // Schedule a daily job which scans installed packages and compiles
124         // those with fresh profiling data.
125         js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
126                     .setRequiresDeviceIdle(true)
127                     .setRequiresCharging(true)
128                     .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
129                     .build());
130 
131         if (DEBUG_DEXOPT) {
132             Log.i(TAG, "Jobs scheduled");
133         }
134     }
135 
notifyPackageChanged(String packageName)136     public static void notifyPackageChanged(String packageName) {
137         // The idle maintanance job skips packages which previously failed to
138         // compile. The given package has changed and may successfully compile
139         // now. Remove it from the list of known failing packages.
140         synchronized (sFailedPackageNamesPrimary) {
141             sFailedPackageNamesPrimary.remove(packageName);
142         }
143         synchronized (sFailedPackageNamesSecondary) {
144             sFailedPackageNamesSecondary.remove(packageName);
145         }
146     }
147 
148     // Returns the current battery level as a 0-100 integer.
getBatteryLevel()149     private int getBatteryLevel() {
150         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
151         Intent intent = registerReceiver(null, filter);
152         int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
153         int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
154         boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
155 
156         if (!present) {
157             // No battery, treat as if 100%, no possibility of draining battery.
158             return 100;
159         }
160 
161         if (level < 0 || scale <= 0) {
162             // Battery data unavailable. This should never happen, so assume the worst.
163             return 0;
164         }
165 
166         return (100 * level / scale);
167     }
168 
getLowStorageThreshold(Context context)169     private long getLowStorageThreshold(Context context) {
170         @SuppressWarnings("deprecation")
171         final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
172         if (lowThreshold == 0) {
173             Log.e(TAG, "Invalid low storage threshold");
174         }
175 
176         return lowThreshold;
177     }
178 
runPostBootUpdate(final JobParameters jobParams, final PackageManagerService pm, final ArraySet<String> pkgs)179     private boolean runPostBootUpdate(final JobParameters jobParams,
180             final PackageManagerService pm, final ArraySet<String> pkgs) {
181         if (mExitPostBootUpdate.get()) {
182             // This job has already been superseded. Do not start it.
183             return false;
184         }
185         new Thread("BackgroundDexOptService_PostBootUpdate") {
186             @Override
187             public void run() {
188                 postBootUpdate(jobParams, pm, pkgs);
189             }
190 
191         }.start();
192         return true;
193     }
194 
postBootUpdate(JobParameters jobParams, PackageManagerService pm, ArraySet<String> pkgs)195     private void postBootUpdate(JobParameters jobParams, PackageManagerService pm,
196             ArraySet<String> pkgs) {
197         // Load low battery threshold from the system config. This is a 0-100 integer.
198         final int lowBatteryThreshold = getResources().getInteger(
199                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
200         final long lowThreshold = getLowStorageThreshold(this);
201 
202         mAbortPostBootUpdate.set(false);
203 
204         ArraySet<String> updatedPackages = new ArraySet<>();
205         for (String pkg : pkgs) {
206             if (mAbortPostBootUpdate.get()) {
207                 // JobScheduler requested an early abort.
208                 return;
209             }
210             if (mExitPostBootUpdate.get()) {
211                 // Different job, which supersedes this one, is running.
212                 break;
213             }
214             if (getBatteryLevel() < lowBatteryThreshold) {
215                 // Rather bail than completely drain the battery.
216                 break;
217             }
218             long usableSpace = mDataDir.getUsableSpace();
219             if (usableSpace < lowThreshold) {
220                 // Rather bail than completely fill up the disk.
221                 Log.w(TAG, "Aborting background dex opt job due to low storage: " +
222                         usableSpace);
223                 break;
224             }
225 
226             if (DEBUG_DEXOPT) {
227                 Log.i(TAG, "Updating package " + pkg);
228             }
229 
230             // Update package if needed. Note that there can be no race between concurrent
231             // jobs because PackageDexOptimizer.performDexOpt is synchronized.
232 
233             // checkProfiles is false to avoid merging profiles during boot which
234             // might interfere with background compilation (b/28612421).
235             // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
236             // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
237             // trade-off worth doing to save boot time work.
238             int result = pm.performDexOptWithStatus(new DexoptOptions(
239                     pkg,
240                     PackageManagerService.REASON_BOOT,
241                     DexoptOptions.DEXOPT_BOOT_COMPLETE));
242             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
243                 updatedPackages.add(pkg);
244             }
245         }
246         notifyPinService(updatedPackages);
247         // Ran to completion, so we abandon our timeslice and do not reschedule.
248         jobFinished(jobParams, /* reschedule */ false);
249     }
250 
runIdleOptimization(final JobParameters jobParams, final PackageManagerService pm, final ArraySet<String> pkgs)251     private boolean runIdleOptimization(final JobParameters jobParams,
252             final PackageManagerService pm, final ArraySet<String> pkgs) {
253         new Thread("BackgroundDexOptService_IdleOptimization") {
254             @Override
255             public void run() {
256                 int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
257                 if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
258                     Log.w(TAG, "Idle optimizations aborted because of space constraints.");
259                     // If we didn't abort we ran to completion (or stopped because of space).
260                     // Abandon our timeslice and do not reschedule.
261                     jobFinished(jobParams, /* reschedule */ false);
262                 }
263             }
264         }.start();
265         return true;
266     }
267 
268     // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context)269     private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
270             Context context) {
271         Log.i(TAG, "Performing idle optimizations");
272         // If post-boot update is still running, request that it exits early.
273         mExitPostBootUpdate.set(true);
274         mAbortIdleOptimization.set(false);
275 
276         long lowStorageThreshold = getLowStorageThreshold(context);
277         // Optimize primary apks.
278         int result = optimizePackages(pm, pkgs, lowStorageThreshold,
279             /*isForPrimaryDex=*/ true);
280         if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
281             return result;
282         }
283         if (supportSecondaryDex()) {
284             result = reconcileSecondaryDexFiles(pm.getDexManager());
285             if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
286                 return result;
287             }
288             result = optimizePackages(pm, pkgs, lowStorageThreshold,
289                 /*isForPrimaryDex=*/ false);
290         }
291         return result;
292     }
293 
294     /**
295      * Get the size of the directory. It uses recursion to go over all files.
296      * @param f
297      * @return
298      */
getDirectorySize(File f)299     private long getDirectorySize(File f) {
300         long size = 0;
301         if (f.isDirectory()) {
302             for (File file: f.listFiles()) {
303                 size += getDirectorySize(file);
304             }
305         } else {
306             size = f.length();
307         }
308         return size;
309     }
310 
311     /**
312      * Get the size of a package.
313      * @param pkg
314      */
getPackageSize(PackageManagerService pm, String pkg)315     private long getPackageSize(PackageManagerService pm, String pkg) {
316         PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
317         long size = 0;
318         if (info != null && info.applicationInfo != null) {
319             File path = Paths.get(info.applicationInfo.sourceDir).toFile();
320             if (path.isFile()) {
321                 path = path.getParentFile();
322             }
323             size += getDirectorySize(path);
324             if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
325                 for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
326                     path = Paths.get(splitSourceDir).toFile();
327                     if (path.isFile()) {
328                         path = path.getParentFile();
329                     }
330                     size += getDirectorySize(path);
331                 }
332             }
333             return size;
334         }
335         return 0;
336     }
337 
optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold, boolean isForPrimaryDex)338     private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
339             long lowStorageThreshold, boolean isForPrimaryDex) {
340         ArraySet<String> updatedPackages = new ArraySet<>();
341         Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
342         Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
343         // Only downgrade apps when space is low on device.
344         // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
345         // up disk before user hits the actual lowStorageThreshold.
346         final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
347                 lowStorageThreshold;
348         boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
349         Log.d(TAG, "Should Downgrade " + shouldDowngrade);
350         boolean dex_opt_performed = false;
351         for (String pkg : pkgs) {
352             int abort_code = abortIdleOptimizations(lowStorageThreshold);
353             if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
354                 return abort_code;
355             }
356             // Downgrade unused packages.
357             if (unusedPackages.contains(pkg) && shouldDowngrade) {
358                 dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex);
359             } else {
360                 if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
361                     // can't dexopt because of low space.
362                     continue;
363                 }
364                 dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
365             }
366             if (dex_opt_performed) {
367                 updatedPackages.add(pkg);
368             }
369         }
370 
371         notifyPinService(updatedPackages);
372         return OPTIMIZE_PROCESSED;
373     }
374 
375 
376     /**
377      * Try to downgrade the package to a smaller compilation filter.
378      * eg. if the package is in speed-profile the package will be downgraded to verify.
379      * @param pm PackageManagerService
380      * @param pkg The package to be downgraded.
381      * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
382      * @return true if the package was downgraded.
383      */
downgradePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)384     private boolean downgradePackage(PackageManagerService pm, String pkg,
385             boolean isForPrimaryDex) {
386         Log.d(TAG, "Downgrading " + pkg);
387         boolean dex_opt_performed = false;
388         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
389         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
390                 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
391                 | DexoptOptions.DEXOPT_DOWNGRADE;
392         long package_size_before = getPackageSize(pm, pkg);
393 
394         if (isForPrimaryDex) {
395             // This applies for system apps or if packages location is not a directory, i.e.
396             // monolithic install.
397             if (!pm.canHaveOatDir(pkg)) {
398                 // For apps that don't have the oat directory, instead of downgrading,
399                 // remove their compiler artifacts from dalvik cache.
400                 pm.deleteOatArtifactsOfPackage(pkg);
401             } else {
402                 dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
403             }
404         } else {
405             dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
406         }
407 
408         if (dex_opt_performed) {
409             StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
410                     getPackageSize(pm, pkg), /*aggressive=*/ false);
411         }
412         return dex_opt_performed;
413     }
414 
supportSecondaryDex()415     private boolean supportSecondaryDex() {
416         return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
417     }
418 
reconcileSecondaryDexFiles(DexManager dm)419     private int reconcileSecondaryDexFiles(DexManager dm) {
420         // TODO(calin): should we blacklist packages for which we fail to reconcile?
421         for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
422             if (mAbortIdleOptimization.get()) {
423                 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
424             }
425             dm.reconcileSecondaryDexFiles(p);
426         }
427         return OPTIMIZE_PROCESSED;
428     }
429 
430     /**
431      *
432      * Optimize package if needed. Note that there can be no race between
433      * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
434      * @param pm An instance of PackageManagerService
435      * @param pkg The package to be downgraded.
436      * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
437      * @return true if the package was downgraded.
438      */
optimizePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)439     private boolean optimizePackage(PackageManagerService pm, String pkg,
440             boolean isForPrimaryDex) {
441         int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
442         int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
443                 | DexoptOptions.DEXOPT_BOOT_COMPLETE
444                 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
445 
446         return isForPrimaryDex
447             ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
448             : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
449     }
450 
performDexOptPrimary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)451     private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
452             int dexoptFlags) {
453         int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
454                 () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
455         return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
456     }
457 
performDexOptSecondary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)458     private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
459             int dexoptFlags) {
460         DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
461                 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
462         int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
463                 () -> pm.performDexOpt(dexoptOptions)
464                     ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
465         );
466         return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
467     }
468 
469     /**
470      * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
471      * the package is added to the list of failed packages.
472      * Return one of following result:
473      *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
474      *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
475      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
476      */
trackPerformDexOpt(String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper)477     private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
478             Supplier<Integer> performDexOptWrapper) {
479         ArraySet<String> sFailedPackageNames =
480                 isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
481         synchronized (sFailedPackageNames) {
482             if (sFailedPackageNames.contains(pkg)) {
483                 // Skip previously failing package
484                 return PackageDexOptimizer.DEX_OPT_SKIPPED;
485             }
486             sFailedPackageNames.add(pkg);
487         }
488         int result = performDexOptWrapper.get();
489         if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
490             synchronized (sFailedPackageNames) {
491                 sFailedPackageNames.remove(pkg);
492             }
493         }
494         return result;
495     }
496 
497     // Evaluate whether or not idle optimizations should continue.
abortIdleOptimizations(long lowStorageThreshold)498     private int abortIdleOptimizations(long lowStorageThreshold) {
499         if (mAbortIdleOptimization.get()) {
500             // JobScheduler requested an early abort.
501             return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
502         }
503         long usableSpace = mDataDir.getUsableSpace();
504         if (usableSpace < lowStorageThreshold) {
505             // Rather bail than completely fill up the disk.
506             Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
507             return OPTIMIZE_ABORT_NO_SPACE_LEFT;
508         }
509 
510         return OPTIMIZE_CONTINUE;
511     }
512 
513     // Evaluate whether apps should be downgraded.
shouldDowngrade(long lowStorageThresholdForDowngrade)514     private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
515         long usableSpace = mDataDir.getUsableSpace();
516         if (usableSpace < lowStorageThresholdForDowngrade) {
517             return true;
518         }
519 
520         return false;
521     }
522 
523     /**
524      * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
525      * then execute on all packages.
526      */
runIdleOptimizationsNow(PackageManagerService pm, Context context, @Nullable List<String> packageNames)527     public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
528             @Nullable List<String> packageNames) {
529         // Create a new object to make sure we don't interfere with the scheduled jobs.
530         // Note that this may still run at the same time with the job scheduled by the
531         // JobScheduler but the scheduler will not be able to cancel it.
532         BackgroundDexOptService bdos = new BackgroundDexOptService();
533         ArraySet<String> packagesToOptimize;
534         if (packageNames == null) {
535             packagesToOptimize = pm.getOptimizablePackages();
536         } else {
537             packagesToOptimize = new ArraySet<>(packageNames);
538         }
539         int result = bdos.idleOptimization(pm, packagesToOptimize, context);
540         return result == OPTIMIZE_PROCESSED;
541     }
542 
543     @Override
onStartJob(JobParameters params)544     public boolean onStartJob(JobParameters params) {
545         if (DEBUG_DEXOPT) {
546             Log.i(TAG, "onStartJob");
547         }
548 
549         // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
550         // the checks above. This check is not "live" - the value is determined by a background
551         // restart with a period of ~1 minute.
552         PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
553         if (pm.isStorageLow()) {
554             if (DEBUG_DEXOPT) {
555                 Log.i(TAG, "Low storage, skipping this run");
556             }
557             return false;
558         }
559 
560         final ArraySet<String> pkgs = pm.getOptimizablePackages();
561         if (pkgs.isEmpty()) {
562             if (DEBUG_DEXOPT) {
563                 Log.i(TAG, "No packages to optimize");
564             }
565             return false;
566         }
567 
568         boolean result;
569         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
570             result = runPostBootUpdate(params, pm, pkgs);
571         } else {
572             result = runIdleOptimization(params, pm, pkgs);
573         }
574 
575         return result;
576     }
577 
578     @Override
onStopJob(JobParameters params)579     public boolean onStopJob(JobParameters params) {
580         if (DEBUG_DEXOPT) {
581             Log.i(TAG, "onStopJob");
582         }
583 
584         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
585             mAbortPostBootUpdate.set(true);
586 
587             // Do not reschedule.
588             // TODO: We should reschedule if we didn't process all apps, yet.
589             return false;
590         } else {
591             mAbortIdleOptimization.set(true);
592 
593             // Reschedule the run.
594             // TODO: Should this be dependent on the stop reason?
595             return true;
596         }
597     }
598 
notifyPinService(ArraySet<String> updatedPackages)599     private void notifyPinService(ArraySet<String> updatedPackages) {
600         PinnerService pinnerService = LocalServices.getService(PinnerService.class);
601         if (pinnerService != null) {
602             Log.i(TAG, "Pinning optimized code " + updatedPackages);
603             pinnerService.update(updatedPackages, false /* force */);
604         }
605     }
606 
getDowngradeUnusedAppsThresholdInMillis()607     private static long getDowngradeUnusedAppsThresholdInMillis() {
608         final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
609         String sysPropValue = SystemProperties.get(sysPropKey);
610         if (sysPropValue == null || sysPropValue.isEmpty()) {
611             Log.w(TAG, "SysProp " + sysPropKey + " not set");
612             return Long.MAX_VALUE;
613         }
614         return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
615     }
616 
isBackgroundDexoptDisabled()617     private static boolean isBackgroundDexoptDisabled() {
618         return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
619                 false /* default */);
620     }
621 }
622