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