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 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
21 import static com.android.server.pm.dex.ArtStatsLogUtils.BackgroundDexoptJobStatsLogger;
22 
23 import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
24 
25 import android.annotation.IntDef;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.job.JobInfo;
29 import android.app.job.JobParameters;
30 import android.app.job.JobScheduler;
31 import android.content.BroadcastReceiver;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.pm.PackageInfo;
37 import android.os.BatteryManagerInternal;
38 import android.os.Binder;
39 import android.os.Environment;
40 import android.os.IThermalService;
41 import android.os.PowerManager;
42 import android.os.Process;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.SystemClock;
46 import android.os.SystemProperties;
47 import android.os.Trace;
48 import android.os.UserHandle;
49 import android.os.storage.StorageManager;
50 import android.util.ArraySet;
51 import android.util.Log;
52 import android.util.Slog;
53 
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.util.ArrayUtils;
57 import com.android.internal.util.FrameworkStatsLog;
58 import com.android.internal.util.FunctionalUtils.ThrowingCheckedSupplier;
59 import com.android.internal.util.IndentingPrintWriter;
60 import com.android.server.LocalServices;
61 import com.android.server.PinnerService;
62 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
63 import com.android.server.pm.PackageDexOptimizer.DexOptResult;
64 import com.android.server.pm.dex.DexManager;
65 import com.android.server.pm.dex.DexoptOptions;
66 import com.android.server.utils.TimingsTraceAndSlog;
67 
68 import java.io.File;
69 import java.lang.annotation.Retention;
70 import java.lang.annotation.RetentionPolicy;
71 import java.nio.file.Paths;
72 import java.util.ArrayList;
73 import java.util.List;
74 import java.util.Set;
75 import java.util.concurrent.TimeUnit;
76 
77 /**
78  * Controls background dex optimization run as idle job or command line.
79  */
80 public final class BackgroundDexOptService {
81     private static final String TAG = "BackgroundDexOptService";
82 
83     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
84 
85     @VisibleForTesting static final int JOB_IDLE_OPTIMIZE = 800;
86     @VisibleForTesting static final int JOB_POST_BOOT_UPDATE = 801;
87 
88     private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
89 
90     private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
91 
92     private static ComponentName sDexoptServiceName =
93             new ComponentName("android", BackgroundDexOptJobService.class.getName());
94 
95     // Possible return codes of individual optimization steps.
96     /** Initial value. */
97     public static final int STATUS_UNSPECIFIED = -1;
98     /** Ok status: Optimizations finished, All packages were processed, can continue */
99     public static final int STATUS_OK = 0;
100     /** Optimizations should be aborted. Job scheduler requested it. */
101     public static final int STATUS_ABORT_BY_CANCELLATION = 1;
102     /** Optimizations should be aborted. No space left on device. */
103     public static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
104     /** Optimizations should be aborted. Thermal throttling level too high. */
105     public static final int STATUS_ABORT_THERMAL = 3;
106     /** Battery level too low */
107     public static final int STATUS_ABORT_BATTERY = 4;
108     /**
109      * {@link PackageDexOptimizer#DEX_OPT_FAILED} case. This state means some packages have failed
110      * compilation during the job. Note that the failure will not be permanent as the next dexopt
111      * job will exclude those failed packages.
112      */
113     public static final int STATUS_DEX_OPT_FAILED = 5;
114     /** Encountered fatal error, such as a runtime exception. */
115     public static final int STATUS_FATAL_ERROR = 6;
116 
117     @IntDef(prefix = {"STATUS_"},
118             value =
119                     {
120                             STATUS_UNSPECIFIED,
121                             STATUS_OK,
122                             STATUS_ABORT_BY_CANCELLATION,
123                             STATUS_ABORT_NO_SPACE_LEFT,
124                             STATUS_ABORT_THERMAL,
125                             STATUS_ABORT_BATTERY,
126                             STATUS_DEX_OPT_FAILED,
127                             STATUS_FATAL_ERROR,
128                     })
129     @Retention(RetentionPolicy.SOURCE)
130     public @interface Status {}
131 
132     // Used for calculating space threshold for downgrading unused apps.
133     private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
134 
135     // Thermal cutoff value used if one isn't defined by a system property.
136     private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
137 
138     private final Injector mInjector;
139 
140     private final DexOptHelper mDexOptHelper;
141 
142     private final BackgroundDexoptJobStatsLogger mStatsLogger =
143             new BackgroundDexoptJobStatsLogger();
144 
145     private final Object mLock = new Object();
146 
147     // Thread currently running dexopt. This will be null if dexopt is not running.
148     // The thread running dexopt make sure to set this into null when the pending dexopt is
149     // completed.
150     @GuardedBy("mLock") @Nullable private Thread mDexOptThread;
151 
152     // Thread currently cancelling dexopt. This thread is in blocked wait state until
153     // cancellation is done. Only this thread can change states for control. The other threads, if
154     // need to wait for cancellation, should just wait without doing any control.
155     @GuardedBy("mLock") @Nullable private Thread mDexOptCancellingThread;
156 
157     // Tells whether post boot update is completed or not.
158     @GuardedBy("mLock") private boolean mFinishedPostBootUpdate;
159 
160     // True if JobScheduler invocations of dexopt have been disabled.
161     @GuardedBy("mLock") private boolean mDisableJobSchedulerJobs;
162 
163     @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_UNSPECIFIED;
164 
165     @GuardedBy("mLock") private long mLastExecutionStartUptimeMs;
166     @GuardedBy("mLock") private long mLastExecutionDurationMs;
167 
168     // Keeps packages cancelled from PDO for last session. This is for debugging.
169     @GuardedBy("mLock")
170     private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>();
171 
172     /**
173      * Set of failed packages remembered across job runs.
174      */
175     @GuardedBy("mLock")
176     private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>();
177     @GuardedBy("mLock")
178     private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>();
179 
180     private final long mDowngradeUnusedAppsThresholdInMillis;
181 
182     private List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
183 
184     private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
185 
186     /** Listener for monitoring package change due to dexopt. */
187     public interface PackagesUpdatedListener {
188         /** Called when the packages are updated through dexopt */
onPackagesUpdated(ArraySet<String> updatedPackages)189         void onPackagesUpdated(ArraySet<String> updatedPackages);
190     }
191 
BackgroundDexOptService(Context context, DexManager dexManager, PackageManagerService pm)192     public BackgroundDexOptService(Context context, DexManager dexManager, PackageManagerService pm)
193             throws LegacyDexoptDisabledException {
194         this(new Injector(context, dexManager, pm));
195     }
196 
197     @VisibleForTesting
BackgroundDexOptService(Injector injector)198     public BackgroundDexOptService(Injector injector) throws LegacyDexoptDisabledException {
199         Installer.checkLegacyDexoptDisabled();
200         mInjector = injector;
201         mDexOptHelper = mInjector.getDexOptHelper();
202         LocalServices.addService(BackgroundDexOptService.class, this);
203         mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis();
204     }
205 
206     /** Start scheduling job after boot completion */
systemReady()207     public void systemReady() throws LegacyDexoptDisabledException {
208         Installer.checkLegacyDexoptDisabled();
209         if (mInjector.isBackgroundDexOptDisabled()) {
210             return;
211         }
212 
213         mInjector.getContext().registerReceiver(new BroadcastReceiver() {
214             @Override
215             public void onReceive(Context context, Intent intent) {
216                 mInjector.getContext().unregisterReceiver(this);
217                 // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is
218                 // completed.
219                 scheduleAJob(JOB_POST_BOOT_UPDATE);
220                 scheduleAJob(JOB_IDLE_OPTIMIZE);
221                 if (DEBUG) {
222                     Slog.d(TAG, "BootBgDexopt scheduled");
223                 }
224             }
225         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
226     }
227 
228     /** Dump the current state */
dump(IndentingPrintWriter writer)229     public void dump(IndentingPrintWriter writer) {
230         boolean disabled = mInjector.isBackgroundDexOptDisabled();
231         writer.print("enabled:");
232         writer.println(!disabled);
233         if (disabled) {
234             return;
235         }
236         synchronized (mLock) {
237             writer.print("mDexOptThread:");
238             writer.println(mDexOptThread);
239             writer.print("mDexOptCancellingThread:");
240             writer.println(mDexOptCancellingThread);
241             writer.print("mFinishedPostBootUpdate:");
242             writer.println(mFinishedPostBootUpdate);
243             writer.print("mDisableJobSchedulerJobs:");
244             writer.println(mDisableJobSchedulerJobs);
245             writer.print("mLastExecutionStatus:");
246             writer.println(mLastExecutionStatus);
247             writer.print("mLastExecutionStartUptimeMs:");
248             writer.println(mLastExecutionStartUptimeMs);
249             writer.print("mLastExecutionDurationMs:");
250             writer.println(mLastExecutionDurationMs);
251             writer.print("now:");
252             writer.println(SystemClock.elapsedRealtime());
253             writer.print("mLastCancelledPackages:");
254             writer.println(String.join(",", mLastCancelledPackages));
255             writer.print("mFailedPackageNamesPrimary:");
256             writer.println(String.join(",", mFailedPackageNamesPrimary));
257             writer.print("mFailedPackageNamesSecondary:");
258             writer.println(String.join(",", mFailedPackageNamesSecondary));
259         }
260     }
261 
262     /** Gets the instance of the service */
getService()263     public static BackgroundDexOptService getService() {
264         return LocalServices.getService(BackgroundDexOptService.class);
265     }
266 
267     /**
268      * Executes the background dexopt job immediately for selected packages or all packages.
269      *
270      * <p>This is only for shell command and only root or shell user can use this.
271      *
272      * @param packageNames dex optimize the passed packages in the given order, or all packages in
273      *         the default order if null
274      *
275      * @return true if dex optimization is complete. false if the task is cancelled or if there was
276      *         an error.
277      */
runBackgroundDexoptJob(@ullable List<String> packageNames)278     public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames)
279             throws LegacyDexoptDisabledException {
280         enforceRootOrShell();
281         long identity = Binder.clearCallingIdentity();
282         try {
283             synchronized (mLock) {
284                 // Do not cancel and wait for completion if there is pending task.
285                 waitForDexOptThreadToFinishLocked();
286                 resetStatesForNewDexOptRunLocked(Thread.currentThread());
287             }
288             PackageManagerService pm = mInjector.getPackageManagerService();
289             List<String> packagesToOptimize;
290             if (packageNames == null) {
291                 packagesToOptimize = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer());
292             } else {
293                 packagesToOptimize = packageNames;
294             }
295             return runIdleOptimization(pm, packagesToOptimize, /* isPostBootUpdate= */ false);
296         } finally {
297             Binder.restoreCallingIdentity(identity);
298             markDexOptCompleted();
299         }
300     }
301 
302     /**
303      * Cancels currently running any idle optimization tasks started from JobScheduler
304      * or runIdleOptimization call.
305      *
306      * <p>This is only for shell command and only root or shell user can use this.
307      */
cancelBackgroundDexoptJob()308     public void cancelBackgroundDexoptJob() throws LegacyDexoptDisabledException {
309         Installer.checkLegacyDexoptDisabled();
310         enforceRootOrShell();
311         Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
312     }
313 
314     /**
315      * Sets a flag that disables jobs from being started from JobScheduler.
316      *
317      * This state is not persistent and is only retained in this service instance.
318      *
319      * This is intended for shell command use and only root or shell users can call it.
320      *
321      * @param disable True if JobScheduler invocations should be disabled, false otherwise.
322      */
setDisableJobSchedulerJobs(boolean disable)323     public void setDisableJobSchedulerJobs(boolean disable) throws LegacyDexoptDisabledException {
324         Installer.checkLegacyDexoptDisabled();
325         enforceRootOrShell();
326         synchronized (mLock) {
327             mDisableJobSchedulerJobs = disable;
328         }
329     }
330 
331     /** Adds listener for package update */
addPackagesUpdatedListener(PackagesUpdatedListener listener)332     public void addPackagesUpdatedListener(PackagesUpdatedListener listener)
333             throws LegacyDexoptDisabledException {
334         // TODO(b/251903639): Evaluate whether this needs to support ART Service or not.
335         Installer.checkLegacyDexoptDisabled();
336         synchronized (mLock) {
337             mPackagesUpdatedListeners.add(listener);
338         }
339     }
340 
341     /** Removes package update listener */
removePackagesUpdatedListener(PackagesUpdatedListener listener)342     public void removePackagesUpdatedListener(PackagesUpdatedListener listener)
343             throws LegacyDexoptDisabledException {
344         Installer.checkLegacyDexoptDisabled();
345         synchronized (mLock) {
346             mPackagesUpdatedListeners.remove(listener);
347         }
348     }
349 
350     /**
351      * Notifies package change and removes the package from the failed package list so that
352      * the package can run dexopt again.
353      */
notifyPackageChanged(String packageName)354     public void notifyPackageChanged(String packageName) throws LegacyDexoptDisabledException {
355         Installer.checkLegacyDexoptDisabled();
356         // The idle maintenance job skips packages which previously failed to
357         // compile. The given package has changed and may successfully compile
358         // now. Remove it from the list of known failing packages.
359         synchronized (mLock) {
360             mFailedPackageNamesPrimary.remove(packageName);
361             mFailedPackageNamesSecondary.remove(packageName);
362         }
363     }
364 
365     /** For BackgroundDexOptJobService to dispatch onStartJob event */
onStartJob(BackgroundDexOptJobService job, JobParameters params)366     /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) {
367         Slog.i(TAG, "onStartJob:" + params.getJobId());
368 
369         boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE;
370         // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
371         // the checks above. This check is not "live" - the value is determined by a background
372         // restart with a period of ~1 minute.
373         PackageManagerService pm = mInjector.getPackageManagerService();
374         if (pm.isStorageLow()) {
375             Slog.w(TAG, "Low storage, skipping this run");
376             markPostBootUpdateCompleted(params);
377             return false;
378         }
379 
380         List<String> pkgs = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer());
381         if (pkgs.isEmpty()) {
382             Slog.i(TAG, "No packages to optimize");
383             markPostBootUpdateCompleted(params);
384             return false;
385         }
386 
387         mThermalStatusCutoff = mInjector.getDexOptThermalCutoff();
388 
389         synchronized (mLock) {
390             if (mDisableJobSchedulerJobs) {
391                 Slog.i(TAG, "JobScheduler invocations disabled");
392                 return false;
393             }
394             if (mDexOptThread != null && mDexOptThread.isAlive()) {
395                 // Other task is already running.
396                 return false;
397             }
398             if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) {
399                 // Post boot job not finished yet. Run post boot job first.
400                 return false;
401             }
402             try {
403                 resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
404                         "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
405                         () -> {
406                             TimingsTraceAndSlog tr =
407                                     new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_DALVIK);
408                             tr.traceBegin("jobExecution");
409                             boolean completed = false;
410                             boolean fatalError = false;
411                             try {
412                                 completed = runIdleOptimization(
413                                         pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE);
414                             } catch (LegacyDexoptDisabledException e) {
415                                 Slog.wtf(TAG, e);
416                             } catch (RuntimeException e) {
417                                 fatalError = true;
418                                 throw e;
419                             } finally { // Those cleanup should be done always.
420                                 tr.traceEnd();
421                                 Slog.i(TAG,
422                                         "dexopt finishing. jobid:" + params.getJobId()
423                                                 + " completed:" + completed);
424 
425                                 writeStatsLog(params);
426 
427                                 if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
428                                     if (completed) {
429                                         markPostBootUpdateCompleted(params);
430                                     }
431                                 }
432                                 // Reschedule when cancelled. No need to reschedule when failed with
433                                 // fatal error because it's likely to fail again.
434                                 job.jobFinished(params, !completed && !fatalError);
435                                 markDexOptCompleted();
436                             }
437                         }));
438             } catch (LegacyDexoptDisabledException e) {
439                 Slog.wtf(TAG, e);
440             }
441         }
442         return true;
443     }
444 
445     /** For BackgroundDexOptJobService to dispatch onStopJob event */
onStopJob(BackgroundDexOptJobService job, JobParameters params)446     /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
447         Slog.i(TAG, "onStopJob:" + params.getJobId());
448         // This cannot block as it is in main thread, thus dispatch to a newly created thread
449         // and cancel it from there. As this event does not happen often, creating a new thread
450         // is justified rather than having one thread kept permanently.
451         mInjector.createAndStartThread("DexOptCancel", () -> {
452             try {
453                 cancelDexOptAndWaitForCompletion();
454             } catch (LegacyDexoptDisabledException e) {
455                 Slog.wtf(TAG, e);
456             }
457         });
458         // Always reschedule for cancellation.
459         return true;
460     }
461 
462     /**
463      * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
464      * until cancellation is done.
465      */
cancelDexOptAndWaitForCompletion()466     private void cancelDexOptAndWaitForCompletion() throws LegacyDexoptDisabledException {
467         synchronized (mLock) {
468             if (mDexOptThread == null) {
469                 return;
470             }
471             if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) {
472                 // No control, just wait
473                 waitForDexOptThreadToFinishLocked();
474                 // Do not wait for other cancellation's complete. That will be handled by the next
475                 // start flow.
476                 return;
477             }
478             mDexOptCancellingThread = Thread.currentThread();
479             // Take additional caution to make sure that we do not leave this call
480             // with controlDexOptBlockingLocked(true) state.
481             try {
482                 controlDexOptBlockingLocked(true);
483                 waitForDexOptThreadToFinishLocked();
484             } finally {
485                 // Reset to default states regardless of previous states
486                 mDexOptCancellingThread = null;
487                 mDexOptThread = null;
488                 controlDexOptBlockingLocked(false);
489                 mLock.notifyAll();
490             }
491         }
492     }
493 
494     @GuardedBy("mLock")
waitForDexOptThreadToFinishLocked()495     private void waitForDexOptThreadToFinishLocked() {
496         TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
497         // This tracing section doesn't have any correspondence in ART Service - it never waits for
498         // cancellation to finish.
499         tr.traceBegin("waitForDexOptThreadToFinishLocked");
500         try {
501             // Wait but check in regular internal to see if the thread is still alive.
502             while (mDexOptThread != null && mDexOptThread.isAlive()) {
503                 mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS);
504             }
505         } catch (InterruptedException e) {
506             Slog.w(TAG, "Interrupted while waiting for dexopt thread");
507             Thread.currentThread().interrupt();
508         }
509         tr.traceEnd();
510     }
511 
markDexOptCompleted()512     private void markDexOptCompleted() {
513         synchronized (mLock) {
514             if (mDexOptThread != Thread.currentThread()) {
515                 throw new IllegalStateException(
516                         "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread
517                         + " current:" + Thread.currentThread());
518             }
519             mDexOptThread = null;
520             // Other threads may be waiting for completion.
521             mLock.notifyAll();
522         }
523     }
524 
525     @GuardedBy("mLock")
resetStatesForNewDexOptRunLocked(Thread thread)526     private void resetStatesForNewDexOptRunLocked(Thread thread)
527             throws LegacyDexoptDisabledException {
528         mDexOptThread = thread;
529         mLastCancelledPackages.clear();
530         controlDexOptBlockingLocked(false);
531     }
532 
enforceRootOrShell()533     private void enforceRootOrShell() {
534         int uid = mInjector.getCallingUid();
535         if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
536             throw new SecurityException("Should be shell or root user");
537         }
538     }
539 
540     @GuardedBy("mLock")
controlDexOptBlockingLocked(boolean block)541     private void controlDexOptBlockingLocked(boolean block) throws LegacyDexoptDisabledException {
542         PackageManagerService pm = mInjector.getPackageManagerService();
543         mDexOptHelper.controlDexOptBlocking(block);
544     }
545 
scheduleAJob(int jobId)546     private void scheduleAJob(int jobId) {
547         JobScheduler js = mInjector.getJobScheduler();
548         JobInfo.Builder builder =
549                 new JobInfo.Builder(jobId, sDexoptServiceName).setRequiresDeviceIdle(true);
550         if (jobId == JOB_IDLE_OPTIMIZE) {
551             builder.setRequiresCharging(true).setPeriodic(IDLE_OPTIMIZATION_PERIOD);
552         }
553         js.schedule(builder.build());
554     }
555 
getLowStorageThreshold()556     private long getLowStorageThreshold() {
557         long lowThreshold = mInjector.getDataDirStorageLowBytes();
558         if (lowThreshold == 0) {
559             Slog.e(TAG, "Invalid low storage threshold");
560         }
561 
562         return lowThreshold;
563     }
564 
logStatus(int status)565     private void logStatus(int status) {
566         switch (status) {
567             case STATUS_OK:
568                 Slog.i(TAG, "Idle optimizations completed.");
569                 break;
570             case STATUS_ABORT_NO_SPACE_LEFT:
571                 Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
572                 break;
573             case STATUS_ABORT_BY_CANCELLATION:
574                 Slog.w(TAG, "Idle optimizations aborted by cancellation.");
575                 break;
576             case STATUS_ABORT_THERMAL:
577                 Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
578                 break;
579             case STATUS_ABORT_BATTERY:
580                 Slog.w(TAG, "Idle optimizations aborted by low battery.");
581                 break;
582             case STATUS_DEX_OPT_FAILED:
583                 Slog.w(TAG, "Idle optimizations failed from dexopt.");
584                 break;
585             default:
586                 Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status);
587                 break;
588         }
589     }
590 
591     /**
592      * Returns whether we've successfully run the job. Note that it will return true even if some
593      * packages may have failed compiling.
594      */
runIdleOptimization(PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate)595     private boolean runIdleOptimization(PackageManagerService pm, List<String> pkgs,
596             boolean isPostBootUpdate) throws LegacyDexoptDisabledException {
597         synchronized (mLock) {
598             mLastExecutionStatus = STATUS_UNSPECIFIED;
599             mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
600             mLastExecutionDurationMs = -1;
601         }
602 
603         int status = STATUS_UNSPECIFIED;
604         try {
605             long lowStorageThreshold = getLowStorageThreshold();
606             status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
607             logStatus(status);
608             return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
609         } catch (RuntimeException e) {
610             status = STATUS_FATAL_ERROR;
611             throw e;
612         } finally {
613             synchronized (mLock) {
614                 mLastExecutionStatus = status;
615                 mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
616             }
617         }
618     }
619 
620     /** Gets the size of the directory. It uses recursion to go over all files. */
getDirectorySize(File f)621     private long getDirectorySize(File f) {
622         long size = 0;
623         if (f.isDirectory()) {
624             for (File file : f.listFiles()) {
625                 size += getDirectorySize(file);
626             }
627         } else {
628             size = f.length();
629         }
630         return size;
631     }
632 
633     /** Gets the size of a package. */
getPackageSize(@onNull Computer snapshot, String pkg)634     private long getPackageSize(@NonNull Computer snapshot, String pkg) {
635         // TODO(b/251903639): Make this in line with the calculation in
636         // `DexOptHelper.DexoptDoneHandler`.
637         PackageInfo info = snapshot.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
638         long size = 0;
639         if (info != null && info.applicationInfo != null) {
640             File path = Paths.get(info.applicationInfo.sourceDir).toFile();
641             if (path.isFile()) {
642                 path = path.getParentFile();
643             }
644             size += getDirectorySize(path);
645             if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
646                 for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
647                     File pathSplitSourceDir = Paths.get(splitSourceDir).toFile();
648                     if (pathSplitSourceDir.isFile()) {
649                         pathSplitSourceDir = pathSplitSourceDir.getParentFile();
650                     }
651                     if (path.getAbsolutePath().equals(pathSplitSourceDir.getAbsolutePath())) {
652                         continue;
653                     }
654                     size += getDirectorySize(pathSplitSourceDir);
655                 }
656             }
657             return size;
658         }
659         return 0;
660     }
661 
662     @Status
idleOptimizePackages(PackageManagerService pm, List<String> pkgs, long lowStorageThreshold, boolean isPostBootUpdate)663     private int idleOptimizePackages(PackageManagerService pm, List<String> pkgs,
664             long lowStorageThreshold, boolean isPostBootUpdate)
665             throws LegacyDexoptDisabledException {
666         ArraySet<String> updatedPackages = new ArraySet<>();
667 
668         try {
669             boolean supportSecondaryDex = mInjector.supportSecondaryDex();
670 
671             if (supportSecondaryDex) {
672                 @Status int result = reconcileSecondaryDexFiles();
673                 if (result != STATUS_OK) {
674                     return result;
675                 }
676             }
677 
678             // Only downgrade apps when space is low on device.
679             // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
680             // up disk before user hits the actual lowStorageThreshold.
681             long lowStorageThresholdForDowngrade =
682                     LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * lowStorageThreshold;
683             boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
684             if (DEBUG) {
685                 Slog.d(TAG, "Should Downgrade " + shouldDowngrade);
686             }
687             if (shouldDowngrade) {
688                 final Computer snapshot = pm.snapshotComputer();
689                 Set<String> unusedPackages =
690                         snapshot.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
691                 if (DEBUG) {
692                     Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
693                 }
694 
695                 if (!unusedPackages.isEmpty()) {
696                     for (String pkg : unusedPackages) {
697                         @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
698                         if (abortCode != STATUS_OK) {
699                             // Should be aborted by the scheduler.
700                             return abortCode;
701                         }
702                         @DexOptResult
703                         int downgradeResult = downgradePackage(snapshot, pm, pkg,
704                                 /* isForPrimaryDex= */ true, isPostBootUpdate);
705                         if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
706                             updatedPackages.add(pkg);
707                         }
708                         @Status
709                         int status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
710                         if (status != STATUS_OK) {
711                             return status;
712                         }
713                         if (supportSecondaryDex) {
714                             downgradeResult = downgradePackage(snapshot, pm, pkg,
715                                     /* isForPrimaryDex= */ false, isPostBootUpdate);
716                             status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
717                             if (status != STATUS_OK) {
718                                 return status;
719                             }
720                         }
721                     }
722 
723                     pkgs = new ArrayList<>(pkgs);
724                     pkgs.removeAll(unusedPackages);
725                 }
726             }
727 
728             return optimizePackages(pkgs, lowStorageThreshold, updatedPackages, isPostBootUpdate);
729         } finally {
730             // Always let the pinner service know about changes.
731             // TODO(b/251903639): ART Service does this for all dexopts, while the code below only
732             // runs for background jobs. We should try to make them behave the same.
733             notifyPinService(updatedPackages);
734             // Only notify IORap the primary dex opt, because we don't want to
735             // invalidate traces unnecessary due to b/161633001 and that it's
736             // better to have a trace than no trace at all.
737             notifyPackagesUpdated(updatedPackages);
738         }
739     }
740 
741     @Status
optimizePackages(List<String> pkgs, long lowStorageThreshold, ArraySet<String> updatedPackages, boolean isPostBootUpdate)742     private int optimizePackages(List<String> pkgs, long lowStorageThreshold,
743             ArraySet<String> updatedPackages, boolean isPostBootUpdate)
744             throws LegacyDexoptDisabledException {
745         boolean supportSecondaryDex = mInjector.supportSecondaryDex();
746 
747         // Keep the error if there is any error from any package.
748         @Status int status = STATUS_OK;
749 
750         // Other than cancellation, all packages will be processed even if an error happens
751         // in a package.
752         for (String pkg : pkgs) {
753             int abortCode = abortIdleOptimizations(lowStorageThreshold);
754             if (abortCode != STATUS_OK) {
755                 // Either aborted by the scheduler or no space left.
756                 return abortCode;
757             }
758 
759             @DexOptResult
760             int primaryResult = optimizePackage(pkg, true /* isForPrimaryDex */, isPostBootUpdate);
761             if (primaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
762                 return STATUS_ABORT_BY_CANCELLATION;
763             }
764             if (primaryResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
765                 updatedPackages.add(pkg);
766             } else if (primaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
767                 status = convertPackageDexOptimizerStatusToInternal(primaryResult);
768             }
769 
770             if (!supportSecondaryDex) {
771                 continue;
772             }
773 
774             @DexOptResult
775             int secondaryResult =
776                     optimizePackage(pkg, false /* isForPrimaryDex */, isPostBootUpdate);
777             if (secondaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
778                 return STATUS_ABORT_BY_CANCELLATION;
779             }
780             if (secondaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
781                 status = convertPackageDexOptimizerStatusToInternal(secondaryResult);
782             }
783         }
784         return status;
785     }
786 
787     /**
788      * Try to downgrade the package to a smaller compilation filter.
789      * eg. if the package is in speed-profile the package will be downgraded to verify.
790      * @param pm PackageManagerService
791      * @param pkg The package to be downgraded.
792      * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
793      * @return PackageDexOptimizer.DEX_*
794      */
795     @DexOptResult
downgradePackage(@onNull Computer snapshot, PackageManagerService pm, String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)796     private int downgradePackage(@NonNull Computer snapshot, PackageManagerService pm, String pkg,
797             boolean isForPrimaryDex, boolean isPostBootUpdate)
798             throws LegacyDexoptDisabledException {
799         if (DEBUG) {
800             Slog.d(TAG, "Downgrading " + pkg);
801         }
802         if (isCancelling()) {
803             return PackageDexOptimizer.DEX_OPT_CANCELLED;
804         }
805         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
806         String filter = getCompilerFilterForReason(reason);
807         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
808 
809         if (isProfileGuidedCompilerFilter(filter)) {
810             // We don't expect updates in current profiles to be significant here, but
811             // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
812             // unconditionally enabled for profile guided filters when ART Service is called instead
813             // of the legacy PackageDexOptimizer implementation.
814             dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
815         }
816 
817         if (!isPostBootUpdate) {
818             dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
819         }
820 
821         long package_size_before = getPackageSize(snapshot, pkg);
822         int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
823         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
824             // This applies for system apps or if packages location is not a directory, i.e.
825             // monolithic install.
826             if (!pm.canHaveOatDir(snapshot, pkg)) {
827                 // For apps that don't have the oat directory, instead of downgrading,
828                 // remove their compiler artifacts from dalvik cache.
829                 pm.deleteOatArtifactsOfPackage(snapshot, pkg);
830             } else {
831                 result = performDexOptPrimary(pkg, reason, filter, dexoptFlags);
832             }
833         } else {
834             result = performDexOptSecondary(pkg, reason, filter, dexoptFlags);
835         }
836 
837         if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
838             final Computer newSnapshot = pm.snapshotComputer();
839             FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before,
840                     getPackageSize(newSnapshot, pkg), /*aggressive=*/false);
841         }
842         return result;
843     }
844 
845     @Status
reconcileSecondaryDexFiles()846     private int reconcileSecondaryDexFiles() throws LegacyDexoptDisabledException {
847         // TODO(calin): should we denylist packages for which we fail to reconcile?
848         for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
849             if (isCancelling()) {
850                 return STATUS_ABORT_BY_CANCELLATION;
851             }
852             mInjector.getDexManager().reconcileSecondaryDexFiles(p);
853         }
854         return STATUS_OK;
855     }
856 
857     /**
858      *
859      * Optimize package if needed. Note that there can be no race between
860      * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
861      * @param pkg The package to be downgraded.
862      * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
863      * @param isPostBootUpdate is post boot update or not.
864      * @return PackageDexOptimizer#DEX_OPT_*
865      */
866     @DexOptResult
optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)867     private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)
868             throws LegacyDexoptDisabledException {
869         int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
870                                       : PackageManagerService.REASON_BACKGROUND_DEXOPT;
871         String filter = getCompilerFilterForReason(reason);
872 
873         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
874         if (!isPostBootUpdate) {
875             dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
876                     | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
877         }
878 
879         if (isProfileGuidedCompilerFilter(filter)) {
880             // Ensure DEXOPT_CHECK_FOR_PROFILES_UPDATES is enabled if the filter is profile guided,
881             // to replicate behaviour that will be unconditionally enabled when ART Service is
882             // called instead of the legacy PackageDexOptimizer implementation.
883             dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
884         }
885 
886         // System server share the same code path as primary dex files.
887         // PackageManagerService will select the right optimization path for it.
888         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
889             return performDexOptPrimary(pkg, reason, filter, dexoptFlags);
890         } else {
891             return performDexOptSecondary(pkg, reason, filter, dexoptFlags);
892         }
893     }
894 
895     @DexOptResult
performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags)896     private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags)
897             throws LegacyDexoptDisabledException {
898         DexoptOptions dexoptOptions =
899                 new DexoptOptions(pkg, reason, filter, /*splitName=*/null, dexoptFlags);
900         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true,
901                 () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions));
902     }
903 
904     @DexOptResult
performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags)905     private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags)
906             throws LegacyDexoptDisabledException {
907         DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, filter, /*splitName=*/null,
908                 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
909         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false,
910                 ()
911                         -> mDexOptHelper.performDexOpt(dexoptOptions)
912                         ? PackageDexOptimizer.DEX_OPT_PERFORMED
913                         : PackageDexOptimizer.DEX_OPT_FAILED);
914     }
915 
916     /**
917      * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
918      * the package is added to the list of failed packages.
919      * Return one of following result:
920      *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
921      *  {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
922      *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
923      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
924      */
925     @DexOptResult
trackPerformDexOpt(String pkg, boolean isForPrimaryDex, ThrowingCheckedSupplier<Integer, LegacyDexoptDisabledException> performDexOptWrapper)926     private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
927             ThrowingCheckedSupplier<Integer, LegacyDexoptDisabledException> performDexOptWrapper)
928             throws LegacyDexoptDisabledException {
929         ArraySet<String> failedPackageNames;
930         synchronized (mLock) {
931             failedPackageNames =
932                     isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary;
933             if (failedPackageNames.contains(pkg)) {
934                 // Skip previously failing package
935                 return PackageDexOptimizer.DEX_OPT_SKIPPED;
936             }
937         }
938         int result = performDexOptWrapper.get();
939         if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
940             synchronized (mLock) {
941                 failedPackageNames.add(pkg);
942             }
943         } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) {
944             synchronized (mLock) {
945                 mLastCancelledPackages.add(pkg);
946             }
947         }
948         return result;
949     }
950 
951     @Status
convertPackageDexOptimizerStatusToInternal(@exOptResult int pdoStatus)952     private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) {
953         switch (pdoStatus) {
954             case PackageDexOptimizer.DEX_OPT_CANCELLED:
955                 return STATUS_ABORT_BY_CANCELLATION;
956             case PackageDexOptimizer.DEX_OPT_FAILED:
957                 return STATUS_DEX_OPT_FAILED;
958             case PackageDexOptimizer.DEX_OPT_PERFORMED:
959             case PackageDexOptimizer.DEX_OPT_SKIPPED:
960                 return STATUS_OK;
961             default:
962                 Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus,
963                         new RuntimeException());
964                 return STATUS_DEX_OPT_FAILED;
965         }
966     }
967 
968     /** Evaluate whether or not idle optimizations should continue. */
969     @Status
abortIdleOptimizations(long lowStorageThreshold)970     private int abortIdleOptimizations(long lowStorageThreshold) {
971         if (isCancelling()) {
972             // JobScheduler requested an early abort.
973             return STATUS_ABORT_BY_CANCELLATION;
974         }
975 
976         // Abort background dexopt if the device is in a moderate or stronger thermal throttling
977         // state.
978         int thermalStatus = mInjector.getCurrentThermalStatus();
979         if (DEBUG) {
980             Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
981         }
982         if (thermalStatus >= mThermalStatusCutoff) {
983             return STATUS_ABORT_THERMAL;
984         }
985 
986         if (mInjector.isBatteryLevelLow()) {
987             return STATUS_ABORT_BATTERY;
988         }
989 
990         long usableSpace = mInjector.getDataDirUsableSpace();
991         if (usableSpace < lowStorageThreshold) {
992             // Rather bail than completely fill up the disk.
993             Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
994             return STATUS_ABORT_NO_SPACE_LEFT;
995         }
996 
997         return STATUS_OK;
998     }
999 
1000     // Evaluate whether apps should be downgraded.
shouldDowngrade(long lowStorageThresholdForDowngrade)1001     private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
1002         if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) {
1003             return true;
1004         }
1005 
1006         return false;
1007     }
1008 
isCancelling()1009     private boolean isCancelling() {
1010         synchronized (mLock) {
1011             return mDexOptCancellingThread != null;
1012         }
1013     }
1014 
markPostBootUpdateCompleted(JobParameters params)1015     private void markPostBootUpdateCompleted(JobParameters params) {
1016         if (params.getJobId() != JOB_POST_BOOT_UPDATE) {
1017             return;
1018         }
1019         synchronized (mLock) {
1020             if (!mFinishedPostBootUpdate) {
1021                 mFinishedPostBootUpdate = true;
1022             }
1023         }
1024         // Safe to do this outside lock.
1025         mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE);
1026     }
1027 
notifyPinService(ArraySet<String> updatedPackages)1028     private void notifyPinService(ArraySet<String> updatedPackages) {
1029         PinnerService pinnerService = mInjector.getPinnerService();
1030         if (pinnerService != null) {
1031             Slog.i(TAG, "Pinning optimized code " + updatedPackages);
1032             pinnerService.update(updatedPackages, false /* force */);
1033         }
1034     }
1035 
1036     /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
notifyPackagesUpdated(ArraySet<String> updatedPackages)1037     private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
1038         synchronized (mLock) {
1039             for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) {
1040                 listener.onPackagesUpdated(updatedPackages);
1041             }
1042         }
1043     }
1044 
writeStatsLog(JobParameters params)1045     private void writeStatsLog(JobParameters params) {
1046         @Status int status;
1047         long durationMs;
1048         long durationIncludingSleepMs;
1049         synchronized (mLock) {
1050             status = mLastExecutionStatus;
1051             durationMs = mLastExecutionDurationMs;
1052         }
1053 
1054         mStatsLogger.write(status, params.getStopReason(), durationMs);
1055     }
1056 
1057     /** Injector pattern for testing purpose */
1058     @VisibleForTesting
1059     static final class Injector {
1060         private final Context mContext;
1061         private final DexManager mDexManager;
1062         private final PackageManagerService mPackageManagerService;
1063         private final File mDataDir = Environment.getDataDirectory();
1064 
Injector(Context context, DexManager dexManager, PackageManagerService pm)1065         Injector(Context context, DexManager dexManager, PackageManagerService pm) {
1066             mContext = context;
1067             mDexManager = dexManager;
1068             mPackageManagerService = pm;
1069         }
1070 
getCallingUid()1071         int getCallingUid() {
1072             return Binder.getCallingUid();
1073         }
1074 
getContext()1075         Context getContext() {
1076             return mContext;
1077         }
1078 
getPackageManagerService()1079         PackageManagerService getPackageManagerService() {
1080             return mPackageManagerService;
1081         }
1082 
getDexOptHelper()1083         DexOptHelper getDexOptHelper() {
1084             return new DexOptHelper(getPackageManagerService());
1085         }
1086 
getJobScheduler()1087         JobScheduler getJobScheduler() {
1088             return mContext.getSystemService(JobScheduler.class);
1089         }
1090 
getDexManager()1091         DexManager getDexManager() {
1092             return mDexManager;
1093         }
1094 
getPinnerService()1095         PinnerService getPinnerService() {
1096             return LocalServices.getService(PinnerService.class);
1097         }
1098 
isBackgroundDexOptDisabled()1099         boolean isBackgroundDexOptDisabled() {
1100             return SystemProperties.getBoolean(
1101                     "pm.dexopt.disable_bg_dexopt" /* key */, false /* default */);
1102         }
1103 
isBatteryLevelLow()1104         boolean isBatteryLevelLow() {
1105             return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow();
1106         }
1107 
getDowngradeUnusedAppsThresholdInMillis()1108         long getDowngradeUnusedAppsThresholdInMillis() {
1109             String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
1110             String sysPropValue = SystemProperties.get(sysPropKey);
1111             if (sysPropValue == null || sysPropValue.isEmpty()) {
1112                 Slog.w(TAG, "SysProp " + sysPropKey + " not set");
1113                 return Long.MAX_VALUE;
1114             }
1115             return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
1116         }
1117 
supportSecondaryDex()1118         boolean supportSecondaryDex() {
1119             return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
1120         }
1121 
getDataDirUsableSpace()1122         long getDataDirUsableSpace() {
1123             return mDataDir.getUsableSpace();
1124         }
1125 
getDataDirStorageLowBytes()1126         long getDataDirStorageLowBytes() {
1127             return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir);
1128         }
1129 
getCurrentThermalStatus()1130         int getCurrentThermalStatus() {
1131             IThermalService thermalService = IThermalService.Stub.asInterface(
1132                     ServiceManager.getService(Context.THERMAL_SERVICE));
1133             try {
1134                 return thermalService.getCurrentThermalStatus();
1135             } catch (RemoteException e) {
1136                 return STATUS_ABORT_THERMAL;
1137             }
1138         }
1139 
getDexOptThermalCutoff()1140         int getDexOptThermalCutoff() {
1141             return SystemProperties.getInt(
1142                     "dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
1143         }
1144 
createAndStartThread(String name, Runnable target)1145         Thread createAndStartThread(String name, Runnable target) {
1146             Thread thread = new Thread(target, name);
1147             Slog.i(TAG, "Starting thread:" + name);
1148             thread.start();
1149             return thread;
1150         }
1151     }
1152 }
1153