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