• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.art;
18 
19 import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
20 import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
21 import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
22 import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
23 import static com.android.server.art.ReasonMapping.BatchDexoptReason;
24 import static com.android.server.art.ReasonMapping.BootReason;
25 import static com.android.server.art.Utils.Abi;
26 import static com.android.server.art.model.ArtFlags.GetStatusFlags;
27 import static com.android.server.art.model.ArtFlags.ScheduleStatus;
28 import static com.android.server.art.model.Config.Callback;
29 import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
30 
31 import android.annotation.CallbackExecutor;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.SuppressLint;
35 import android.annotation.SystemApi;
36 import android.annotation.SystemService;
37 import android.app.job.JobInfo;
38 import android.apphibernation.AppHibernationManager;
39 import android.content.Context;
40 import android.os.Binder;
41 import android.os.Build;
42 import android.os.CancellationSignal;
43 import android.os.ParcelFileDescriptor;
44 import android.os.Process;
45 import android.os.RemoteException;
46 import android.os.ServiceSpecificException;
47 import android.os.SystemProperties;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.os.storage.StorageManager;
51 import android.text.TextUtils;
52 import android.util.Log;
53 import android.util.Pair;
54 
55 import androidx.annotation.RequiresApi;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.server.LocalManagerRegistry;
59 import com.android.server.art.model.ArtFlags;
60 import com.android.server.art.model.BatchDexoptParams;
61 import com.android.server.art.model.Config;
62 import com.android.server.art.model.DeleteResult;
63 import com.android.server.art.model.DetailedDexInfo;
64 import com.android.server.art.model.DexoptParams;
65 import com.android.server.art.model.DexoptResult;
66 import com.android.server.art.model.DexoptStatus;
67 import com.android.server.art.model.OperationProgress;
68 import com.android.server.pm.PackageManagerLocal;
69 import com.android.server.pm.pkg.AndroidPackage;
70 import com.android.server.pm.pkg.AndroidPackageSplit;
71 import com.android.server.pm.pkg.PackageState;
72 
73 import dalvik.system.DexFile;
74 
75 import java.io.File;
76 import java.io.FileNotFoundException;
77 import java.io.IOException;
78 import java.io.PrintWriter;
79 import java.nio.file.Files;
80 import java.nio.file.Path;
81 import java.nio.file.Paths;
82 import java.util.ArrayList;
83 import java.util.Arrays;
84 import java.util.Collections;
85 import java.util.Comparator;
86 import java.util.HashSet;
87 import java.util.List;
88 import java.util.Objects;
89 import java.util.Set;
90 import java.util.concurrent.CompletableFuture;
91 import java.util.concurrent.Executor;
92 import java.util.concurrent.ExecutorService;
93 import java.util.concurrent.Executors;
94 import java.util.concurrent.TimeUnit;
95 import java.util.function.Consumer;
96 import java.util.stream.Collectors;
97 import java.util.stream.Stream;
98 
99 /**
100  * This class provides a system API for functionality provided by the ART module.
101  *
102  * Note: Although this class is the entry point of ART services, this class is not a {@link
103  * SystemService}, and it does not publish a binder. Instead, it is a module loaded by the
104  * system_server process, registered in {@link LocalManagerRegistry}. {@link LocalManagerRegistry}
105  * specifies that in-process module interfaces should be named with the suffix {@code ManagerLocal}
106  * for consistency.
107  *
108  * @hide
109  */
110 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
111 public final class ArtManagerLocal {
112     /** @hide */
113     public static final String TAG = "ArtService";
114 
115     private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
116             "BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
117 
118     /** @hide */
119     @VisibleForTesting public static final long DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES = 500_000_000;
120 
121     @NonNull private final Injector mInjector;
122 
123     @Deprecated
ArtManagerLocal()124     public ArtManagerLocal() {
125         mInjector = new Injector(this, null /* context */);
126     }
127 
128     /**
129      * Creates an instance.
130      *
131      * Only {@code SystemServer} should create an instance and register it in {@link
132      * LocalManagerRegistry}. Other API users should obtain the instance from {@link
133      * LocalManagerRegistry}.
134      *
135      * @param context the system server context
136      * @throws NullPointerException if required dependencies are missing
137      */
138     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
ArtManagerLocal(@onNull Context context)139     public ArtManagerLocal(@NonNull Context context) {
140         mInjector = new Injector(this, context);
141     }
142 
143     /** @hide */
144     @VisibleForTesting
ArtManagerLocal(@onNull Injector injector)145     public ArtManagerLocal(@NonNull Injector injector) {
146         mInjector = injector;
147     }
148 
149     /**
150      * Handles ART Service commands, which is a subset of `cmd package` commands.
151      *
152      * Note: This method is not an override of {@link Binder#handleShellCommand} because ART
153      * services does not publish a binder. Instead, it handles the commands forwarded by the
154      * `package` service. The semantics of the parameters are the same as {@link
155      * Binder#handleShellCommand}.
156      *
157      * @return zero on success, non-zero on internal error (e.g., I/O error)
158      * @throws SecurityException if the caller is not root
159      * @throws IllegalArgumentException if the arguments are illegal
160      * @see ArtShellCommand#printHelp(PrintWriter)
161      */
162     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
handleShellCommand(@onNull Binder target, @NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args)163     public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
164             @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
165             @NonNull String[] args) {
166         return new ArtShellCommand(this, mInjector.getPackageManagerLocal())
167                 .exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
168                         err.getFileDescriptor(), args);
169     }
170 
171     /** Prints ART Service shell command help. */
172     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
printShellCommandHelp(@onNull PrintWriter pw)173     public void printShellCommandHelp(@NonNull PrintWriter pw) {
174         ArtShellCommand.printHelp(pw);
175     }
176 
177     /**
178      * Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the
179      * ones for secondary dex files. This includes VDEX, ODEX, and ART files.
180      *
181      * @throws IllegalArgumentException if the package is not found or the flags are illegal
182      * @throws IllegalStateException if the operation encounters an error that should never happen
183      *         (e.g., an internal logic error).
184      */
185     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
186     @NonNull
deleteDexoptArtifacts( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName)187     public DeleteResult deleteDexoptArtifacts(
188             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
189         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
190         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
191 
192         try {
193             long freedBytes = 0;
194 
195             boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd());
196             for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
197                 if (!dexInfo.hasCode()) {
198                     continue;
199                 }
200                 for (Abi abi : Utils.getAllAbis(pkgState)) {
201                     freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
202                             dexInfo.dexPath(), abi.isa(), isInDalvikCache));
203                 }
204             }
205 
206             for (SecondaryDexInfo dexInfo :
207                     mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
208                 for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
209                     freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
210                             dexInfo.dexPath(), abi.isa(), false /* isInDalvikCache */));
211                 }
212             }
213 
214             return DeleteResult.create(freedBytes);
215         } catch (RemoteException e) {
216             Utils.logArtdException(e);
217             return DeleteResult.create(0 /* freedBytes */);
218         }
219     }
220 
221     /**
222      * Returns the dexopt status of a package.
223      *
224      * Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}).
225      *
226      * @throws IllegalArgumentException if the package is not found or the flags are illegal
227      * @throws IllegalStateException if the operation encounters an error that should never happen
228      *         (e.g., an internal logic error).
229      */
230     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
231     @NonNull
getDexoptStatus( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName)232     public DexoptStatus getDexoptStatus(
233             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
234         return getDexoptStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
235     }
236 
237     /**
238      * Same as above, but allows to specify flags.
239      *
240      * @see #getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String)
241      */
242     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
243     @NonNull
getDexoptStatus(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @GetStatusFlags int flags)244     public DexoptStatus getDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
245             @NonNull String packageName, @GetStatusFlags int flags) {
246         if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
247                 && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
248             throw new IllegalArgumentException("Nothing to check");
249         }
250 
251         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
252         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
253 
254         List<Pair<DetailedDexInfo, Abi>> dexAndAbis = new ArrayList<>();
255 
256         if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
257             for (DetailedPrimaryDexInfo dexInfo :
258                     PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
259                 if (!dexInfo.hasCode()) {
260                     continue;
261                 }
262                 for (Abi abi : Utils.getAllAbis(pkgState)) {
263                     dexAndAbis.add(Pair.create(dexInfo, abi));
264                 }
265             }
266         }
267 
268         if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
269             for (SecondaryDexInfo dexInfo :
270                     mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
271                 for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
272                     dexAndAbis.add(Pair.create(dexInfo, abi));
273                 }
274             }
275         }
276 
277         try {
278             List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
279 
280             for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) {
281                 DetailedDexInfo dexInfo = pair.first;
282                 Abi abi = pair.second;
283                 try {
284                     GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
285                             dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
286                     statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
287                             dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(),
288                             abi.name(), result.compilerFilter, result.compilationReason,
289                             result.locationDebugString));
290                 } catch (ServiceSpecificException e) {
291                     statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
292                             dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(),
293                             abi.name(), "error", "error", e.getMessage()));
294                 }
295             }
296 
297             return DexoptStatus.create(statuses);
298         } catch (RemoteException e) {
299             Utils.logArtdException(e);
300             List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
301             for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) {
302                 DetailedDexInfo dexInfo = pair.first;
303                 Abi abi = pair.second;
304                 statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
305                         dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(), abi.name(),
306                         "error", "error", e.getMessage()));
307             }
308             return DexoptStatus.create(statuses);
309         }
310     }
311 
312     /**
313      * Clear the profiles that are collected locally for the given package, including the profiles
314      * for primary and secondary dex files. More specifically, it clears reference profiles and
315      * current profiles. External profiles (e.g., cloud profiles) will be kept.
316      *
317      * @throws IllegalArgumentException if the package is not found or the flags are illegal
318      * @throws IllegalStateException if the operation encounters an error that should never happen
319      *         (e.g., an internal logic error).
320      */
321     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
322     @NonNull
clearAppProfiles( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName)323     public void clearAppProfiles(
324             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
325         PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
326         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
327 
328         try {
329             for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
330                 if (!dexInfo.hasCode()) {
331                     continue;
332                 }
333                 mInjector.getArtd().deleteProfile(
334                         PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
335                 for (ProfilePath profile : PrimaryDexUtils.getCurProfiles(
336                              mInjector.getUserManager(), pkgState, dexInfo)) {
337                     mInjector.getArtd().deleteProfile(profile);
338                 }
339             }
340 
341             // This only deletes the profiles of known secondary dex files. If there are unknown
342             // secondary dex files, their profiles will be deleted by `cleanup`.
343             for (SecondaryDexInfo dexInfo :
344                     mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
345                 mInjector.getArtd().deleteProfile(
346                         AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()));
347                 mInjector.getArtd().deleteProfile(
348                         AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
349             }
350         } catch (RemoteException e) {
351             Utils.logArtdException(e);
352         }
353     }
354 
355     /**
356      * Dexopts a package. The time this operation takes ranges from a few milliseconds to several
357      * minutes, depending on the params and the code size of the package.
358      *
359      * When this operation ends (either completed or cancelled), callbacks added by {@link
360      * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
361      *
362      * @throws IllegalArgumentException if the package is not found or the params are illegal
363      * @throws IllegalStateException if the operation encounters an error that should never happen
364      *         (e.g., an internal logic error).
365      */
366     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
367     @NonNull
dexoptPackage(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull DexoptParams params)368     public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
369             @NonNull String packageName, @NonNull DexoptParams params) {
370         var cancellationSignal = new CancellationSignal();
371         return dexoptPackage(snapshot, packageName, params, cancellationSignal);
372     }
373 
374     /**
375      * Same as above, but supports cancellation.
376      *
377      * @see #dexoptPackage(PackageManagerLocal.FilteredSnapshot, String, DexoptParams)
378      */
379     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
380     @NonNull
dexoptPackage(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)381     public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
382             @NonNull String packageName, @NonNull DexoptParams params,
383             @NonNull CancellationSignal cancellationSignal) {
384         return mInjector.getDexoptHelper().dexopt(
385                 snapshot, List.of(packageName), params, cancellationSignal, Runnable::run);
386     }
387 
388     /**
389      * Resets the dexopt state of the package as if the package is newly installed.
390      *
391      * More specifically, it clears reference profiles, current profiles, and any code compiled from
392      * those local profiles. If there is an external profile (e.g., a cloud profile), the code
393      * compiled from that profile will be kept.
394      *
395      * For secondary dex files, it also clears all dexopt artifacts.
396      *
397      * @hide
398      */
399     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
400     @NonNull
resetDexoptStatus(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @NonNull CancellationSignal cancellationSignal)401     public DexoptResult resetDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
402             @NonNull String packageName, @NonNull CancellationSignal cancellationSignal) {
403         // We must delete the artifacts for primary dex files beforehand rather than relying on
404         // `dexoptPackage` to replace them because:
405         // - If dexopt is not needed after the deletion, then we shouldn't run dexopt at all. For
406         //   example, when we have a DM file that contains a VDEX file but doesn't contain a cloud
407         //   profile, this happens. Note that this is more about correctness rather than
408         //   performance.
409         // - We don't want the existing artifacts to affect dexopt. For example, the existing VDEX
410         //   file should not be an input VDEX.
411         //
412         // We delete the artifacts for secondary dex files and `dexoptPackage` won't re-generate
413         // them because `dexoptPackage` for `REASON_INSTALL` is for primary dex only. This is
414         // intentional because secondary dex files are supposed to be unknown at install time.
415         deleteDexoptArtifacts(snapshot, packageName);
416         clearAppProfiles(snapshot, packageName);
417 
418         // Re-generate artifacts for primary dex files if needed.
419         return dexoptPackage(snapshot, packageName,
420                 new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(), cancellationSignal);
421     }
422 
423     /**
424      * Runs batch dexopt for the given reason.
425      *
426      * This is called by ART Service automatically during boot / background dexopt.
427      *
428      * The list of packages and options are determined by {@code reason}, and can be overridden by
429      * {@link #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}.
430      *
431      * The dexopt is done in a thread pool. The number of packages being dexopted
432      * simultaneously can be configured by system property {@code pm.dexopt.<reason>.concurrency}
433      * (e.g., {@code pm.dexopt.bg-dexopt.concurrency=4}), and the number of threads for each {@code
434      * dex2oat} invocation can be configured by system property {@code dalvik.vm.*dex2oat-threads}
435      * (e.g., {@code dalvik.vm.background-dex2oat-threads=4}). I.e., the maximum number of
436      * concurrent threads is the product of the two system properties. Note that the physical core
437      * usage is always bound by {@code dalvik.vm.*dex2oat-cpu-set} regardless of the number of
438      * threads.
439      *
440      * When this operation ends (either completed or cancelled), callbacks added by {@link
441      * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
442      *
443      * If the storage is nearly low, and {@code reason} is {@link ReasonMapping#REASON_BG_DEXOPT},
444      * it may also downgrade some inactive packages to a less optimized compiler filter, specified
445      * by the system property {@code pm.dexopt.inactive} (typically "verify"), to free up some
446      * space. This feature is only enabled when the system property {@code
447      * pm.dexopt.downgrade_after_inactive_days} is set. The space threshold to trigger this feature
448      * is the Storage Manager's low space threshold plus {@link
449      * #DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES}. The concurrency can be configured by system property
450      * {@code pm.dexopt.bg-dexopt.concurrency}. The packages in the list provided by
451      * {@link BatchDexoptStartCallback} for {@link ReasonMapping#REASON_BG_DEXOPT} are never
452      * downgraded.
453      *
454      * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
455      * @param reason determines the default list of packages and options
456      * @param cancellationSignal provides the ability to cancel this operation
457      * @param processCallbackExecutor the executor to call {@code progressCallback}
458      * @param progressCallback called repeatedly whenever there is an update on the progress
459      * @throws IllegalStateException if the operation encounters an error that should never happen
460      *         (e.g., an internal logic error), or the callback set by {@link
461      *         #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} provides invalid
462      *         params.
463      *
464      * @hide
465      */
466     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
467     @NonNull
dexoptPackages(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull @BatchDexoptReason String reason, @NonNull CancellationSignal cancellationSignal, @Nullable @CallbackExecutor Executor progressCallbackExecutor, @Nullable Consumer<OperationProgress> progressCallback)468     public DexoptResult dexoptPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
469             @NonNull @BatchDexoptReason String reason,
470             @NonNull CancellationSignal cancellationSignal,
471             @Nullable @CallbackExecutor Executor progressCallbackExecutor,
472             @Nullable Consumer<OperationProgress> progressCallback) {
473         List<String> defaultPackages =
474                 Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
475         DexoptParams defaultDexoptParams = new DexoptParams.Builder(reason).build();
476         var builder = new BatchDexoptParams.Builder(defaultPackages, defaultDexoptParams);
477         Callback<BatchDexoptStartCallback, Void> callback =
478                 mInjector.getConfig().getBatchDexoptStartCallback();
479         if (callback != null) {
480             Utils.executeAndWait(callback.executor(), () -> {
481                 callback.get().onBatchDexoptStart(
482                         snapshot, reason, defaultPackages, builder, cancellationSignal);
483             });
484         }
485         BatchDexoptParams params = builder.build();
486         Utils.check(params.getDexoptParams().getReason().equals(reason));
487 
488         ExecutorService dexoptExecutor =
489                 Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason));
490         try {
491             if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
492                 maybeDowngradePackages(snapshot,
493                         new HashSet<>(params.getPackages()) /* excludedPackages */,
494                         cancellationSignal, dexoptExecutor);
495             }
496             Log.i(TAG, "Dexopting packages");
497             return mInjector.getDexoptHelper().dexopt(snapshot, params.getPackages(),
498                     params.getDexoptParams(), cancellationSignal, dexoptExecutor,
499                     progressCallbackExecutor, progressCallback);
500         } finally {
501             dexoptExecutor.shutdown();
502         }
503     }
504 
505     /**
506      * Overrides the default params for {@link #dexoptPackages}. This method is thread-safe.
507      *
508      * This method gives users the opportunity to change the behavior of {@link #dexoptPackages},
509      * which is called by ART Service automatically during boot / background dexopt.
510      *
511      * If this method is not called, the default list of packages and options determined by {@code
512      * reason} will be used.
513      */
514     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
setBatchDexoptStartCallback(@onNull @allbackExecutor Executor executor, @NonNull BatchDexoptStartCallback callback)515     public void setBatchDexoptStartCallback(@NonNull @CallbackExecutor Executor executor,
516             @NonNull BatchDexoptStartCallback callback) {
517         mInjector.getConfig().setBatchDexoptStartCallback(executor, callback);
518     }
519 
520     /**
521      * Clears the callback set by {@link
522      * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}. This method is
523      * thread-safe.
524      */
525     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
clearBatchDexoptStartCallback()526     public void clearBatchDexoptStartCallback() {
527         mInjector.getConfig().clearBatchDexoptStartCallback();
528     }
529 
530     /**
531      * Schedules a background dexopt job. Does nothing if the job is already scheduled.
532      *
533      * Use this method if you want the system to automatically determine the best time to run
534      * dexopt.
535      *
536      * The job will be run by the job scheduler. The job scheduling configuration can be overridden
537      * by {@link
538      * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. By
539      * default, it runs periodically (at most once a day) when all the following constraints are
540      * meet.
541      *
542      * <ul>
543      *   <li>The device is idling. (see {@link JobInfo.Builder#setRequiresDeviceIdle(boolean)})
544      *   <li>The device is charging. (see {@link JobInfo.Builder#setRequiresCharging(boolean)})
545      *   <li>The battery level is not low.
546      *     (see {@link JobInfo.Builder#setRequiresBatteryNotLow(boolean)})
547      * </ul>
548      *
549      * When the job is running, it may be cancelled by the job scheduler immediately whenever one of
550      * the constraints above is no longer met or cancelled by the {@link
551      * #cancelBackgroundDexoptJob()} API. The job scheduler retries it in the next <i>maintenance
552      * window</i>. For information about <i>maintenance window</i>, see
553      * https://developer.android.com/training/monitoring-device-state/doze-standby.
554      *
555      * See {@link #dexoptPackages} for how to customize the behavior of the job.
556      *
557      * When the job ends (either completed or cancelled), the result is sent to the callbacks added
558      * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the
559      * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
560      *
561      * @throws RuntimeException if called during boot before the job scheduler service has started.
562      */
563     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
scheduleBackgroundDexoptJob()564     public @ScheduleStatus int scheduleBackgroundDexoptJob() {
565         return mInjector.getBackgroundDexoptJob().schedule();
566     }
567 
568     /**
569      * Unschedules the background dexopt job scheduled by {@link #scheduleBackgroundDexoptJob()}.
570      * Does nothing if the job is not scheduled.
571      *
572      * Use this method if you no longer want the system to automatically run dexopt.
573      *
574      * If the job is already started by the job scheduler and is running, it will be cancelled
575      * immediately, and the result sent to the callbacks added by {@link
576      * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link
577      * DexoptResult#DEXOPT_CANCELLED}. Note that a job started by {@link
578      * #startBackgroundDexoptJob()} will not be cancelled by this method.
579      */
580     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
unscheduleBackgroundDexoptJob()581     public void unscheduleBackgroundDexoptJob() {
582         mInjector.getBackgroundDexoptJob().unschedule();
583     }
584 
585     /**
586      * Overrides the configuration of the background dexopt job. This method is thread-safe.
587      */
588     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
setScheduleBackgroundDexoptJobCallback(@onNull @allbackExecutor Executor executor, @NonNull ScheduleBackgroundDexoptJobCallback callback)589     public void setScheduleBackgroundDexoptJobCallback(@NonNull @CallbackExecutor Executor executor,
590             @NonNull ScheduleBackgroundDexoptJobCallback callback) {
591         mInjector.getConfig().setScheduleBackgroundDexoptJobCallback(executor, callback);
592     }
593 
594     /**
595      * Clears the callback set by {@link
596      * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. This
597      * method is thread-safe.
598      */
599     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
clearScheduleBackgroundDexoptJobCallback()600     public void clearScheduleBackgroundDexoptJobCallback() {
601         mInjector.getConfig().clearScheduleBackgroundDexoptJobCallback();
602     }
603 
604     /**
605      * Manually starts a background dexopt job. Does nothing if a job is already started by this
606      * method or by the job scheduler. This method is not blocking.
607      *
608      * Unlike the job started by job scheduler, the job started by this method does not respect
609      * constraints described in {@link #scheduleBackgroundDexoptJob()}, and hence will not be
610      * cancelled when they aren't met.
611      *
612      * See {@link #dexoptPackages} for how to customize the behavior of the job.
613      *
614      * When the job ends (either completed or cancelled), the result is sent to the callbacks added
615      * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the
616      * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
617      */
618     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
startBackgroundDexoptJob()619     public void startBackgroundDexoptJob() {
620         mInjector.getBackgroundDexoptJob().start();
621     }
622 
623     /**
624      * Same as above, but also returns a {@link CompletableFuture}.
625      *
626      * @hide
627      */
628     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
629     @NonNull
startBackgroundDexoptJobAndReturnFuture()630     public CompletableFuture<BackgroundDexoptJob.Result> startBackgroundDexoptJobAndReturnFuture() {
631         return mInjector.getBackgroundDexoptJob().start();
632     }
633 
634     /**
635      * Returns the running background dexopt job, or null of no background dexopt job is running.
636      *
637      * @hide
638      */
639     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
640     @Nullable
getRunningBackgroundDexoptJob()641     public CompletableFuture<BackgroundDexoptJob.Result> getRunningBackgroundDexoptJob() {
642         return mInjector.getBackgroundDexoptJob().get();
643     }
644 
645     /**
646      * Cancels the running background dexopt job started by the job scheduler or by {@link
647      * #startBackgroundDexoptJob()}. Does nothing if the job is not running. This method is not
648      * blocking.
649      *
650      * The result sent to the callbacks added by {@link
651      * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link
652      * DexoptResult#DEXOPT_CANCELLED}.
653      */
654     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
cancelBackgroundDexoptJob()655     public void cancelBackgroundDexoptJob() {
656         mInjector.getBackgroundDexoptJob().cancel();
657     }
658 
659     /**
660      * Adds a global listener that listens to any result of dexopting package(s), no matter run
661      * manually or automatically. Calling this method multiple times with different callbacks is
662      * allowed. Callbacks are executed in the same order as the one in which they were added. This
663      * method is thread-safe.
664      *
665      * @param onlyIncludeUpdates if true, the results passed to the callback will only contain
666      *         packages that have any update, and the callback won't be called with results that
667      *         don't have any update.
668      * @throws IllegalStateException if the same callback instance is already added
669      */
670     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
addDexoptDoneCallback(boolean onlyIncludeUpdates, @NonNull @CallbackExecutor Executor executor, @NonNull DexoptDoneCallback callback)671     public void addDexoptDoneCallback(boolean onlyIncludeUpdates,
672             @NonNull @CallbackExecutor Executor executor, @NonNull DexoptDoneCallback callback) {
673         mInjector.getConfig().addDexoptDoneCallback(onlyIncludeUpdates, executor, callback);
674     }
675 
676     /**
677      * Removes the listener added by {@link
678      * #addDexoptDoneCallback(Executor, DexoptDoneCallback)}. Does nothing if the
679      * callback was not added. This method is thread-safe.
680      */
681     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
removeDexoptDoneCallback(@onNull DexoptDoneCallback callback)682     public void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) {
683         mInjector.getConfig().removeDexoptDoneCallback(callback);
684     }
685 
686     /**
687      * Snapshots the profile of the given app split. The profile snapshot is the aggregation of all
688      * existing profiles of the app split (all current user profiles and the reference profile).
689      *
690      * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
691      * @param packageName the name of the app that owns the profile
692      * @param splitName see {@link AndroidPackageSplit#getName()}
693      * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
694      *         caller is responsible for closing it. Note that the content may be empty.
695      * @throws IllegalArgumentException if the package or the split is not found
696      * @throws IllegalStateException if the operation encounters an error that should never happen
697      *         (e.g., an internal logic error).
698      * @throws SnapshotProfileException if the operation encounters an error that the caller should
699      *         handle (e.g., an I/O error, a sub-process crash).
700      */
701     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
702     @NonNull
snapshotAppProfile( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @Nullable String splitName)703     public ParcelFileDescriptor snapshotAppProfile(
704             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
705             @Nullable String splitName) throws SnapshotProfileException {
706         var options = new MergeProfileOptions();
707         options.forceMerge = true;
708         return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options);
709     }
710 
711     /**
712      * Same as above, but outputs in text format.
713      *
714      * @hide
715      */
716     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
717     @NonNull
dumpAppProfile( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @Nullable String splitName, boolean dumpClassesAndMethods)718     public ParcelFileDescriptor dumpAppProfile(
719             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
720             @Nullable String splitName, boolean dumpClassesAndMethods)
721             throws SnapshotProfileException {
722         var options = new MergeProfileOptions();
723         options.dumpOnly = !dumpClassesAndMethods;
724         options.dumpClassesAndMethods = dumpClassesAndMethods;
725         return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options);
726     }
727 
728     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
729     @NonNull
snapshotOrDumpAppProfile( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @Nullable String splitName, @NonNull MergeProfileOptions options)730     private ParcelFileDescriptor snapshotOrDumpAppProfile(
731             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
732             @Nullable String splitName, @NonNull MergeProfileOptions options)
733             throws SnapshotProfileException {
734         try {
735             PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
736             AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
737             PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
738 
739             List<ProfilePath> profiles = new ArrayList<>();
740 
741             Pair<ProfilePath, Boolean> pair = Utils.getOrInitReferenceProfile(mInjector.getArtd(),
742                     dexInfo.dexPath(), PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo),
743                     PrimaryDexUtils.getExternalProfiles(dexInfo),
744                     PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, Process.SYSTEM_UID,
745                             Process.SYSTEM_UID, false /* isPublic */));
746             ProfilePath refProfile = pair != null ? pair.first : null;
747 
748             if (refProfile != null) {
749                 profiles.add(refProfile);
750             }
751 
752             profiles.addAll(
753                     PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
754 
755             OutputProfile output = PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo,
756                     Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
757 
758             try {
759                 return mergeProfilesAndGetFd(profiles, output, List.of(dexInfo.dexPath()), options);
760             } finally {
761                 if (refProfile != null && refProfile.getTag() == ProfilePath.tmpProfilePath) {
762                     mInjector.getArtd().deleteProfile(refProfile);
763                 }
764             }
765         } catch (RemoteException e) {
766             throw new SnapshotProfileException(e);
767         }
768     }
769 
770     /**
771      * Snapshots the boot image profile
772      * (https://source.android.com/docs/core/bootloader/boot-image-profiles). The profile snapshot
773      * is the aggregation of all existing profiles on the device (all current user profiles and
774      * reference profiles) of all apps and the system server filtered by applicable classpaths.
775      *
776      * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
777      * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
778      *         caller is responsible for closing it. Note that the content may be empty.
779      * @throws IllegalStateException if the operation encounters an error that should never happen
780      *         (e.g., an internal logic error).
781      * @throws SnapshotProfileException if the operation encounters an error that the caller should
782      *         handle (e.g., an I/O error, a sub-process crash).
783      */
784     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
785     @NonNull
snapshotBootImageProfile( @onNull PackageManagerLocal.FilteredSnapshot snapshot)786     public ParcelFileDescriptor snapshotBootImageProfile(
787             @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
788             throws SnapshotProfileException {
789         if (!Constants.isBootImageProfilingEnabled()) {
790             throw new SnapshotProfileException("Boot image profiling not enabled");
791         }
792 
793         List<ProfilePath> profiles = new ArrayList<>();
794 
795         // System server profiles.
796         profiles.add(AidlUtils.buildProfilePathForPrimaryRef(
797                 Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY));
798         for (UserHandle handle :
799                 mInjector.getUserManager().getUserHandles(true /* excludeDying */)) {
800             profiles.add(AidlUtils.buildProfilePathForPrimaryCur(handle.getIdentifier(),
801                     Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY));
802         }
803 
804         // App profiles.
805         snapshot.getPackageStates().forEach((packageName, appPkgState) -> {
806             // Hibernating apps can still provide useful profile contents, so skip the hibernation
807             // check.
808             if (Utils.canDexoptPackage(appPkgState, null /* appHibernationManager */)) {
809                 AndroidPackage appPkg = Utils.getPackageOrThrow(appPkgState);
810                 for (PrimaryDexInfo appDexInfo : PrimaryDexUtils.getDexInfo(appPkg)) {
811                     if (!appDexInfo.hasCode()) {
812                         continue;
813                     }
814                     profiles.add(PrimaryDexUtils.buildRefProfilePath(appPkgState, appDexInfo));
815                     profiles.addAll(PrimaryDexUtils.getCurProfiles(
816                             mInjector.getUserManager(), appPkgState, appDexInfo));
817                 }
818             }
819         });
820 
821         OutputProfile output = AidlUtils.buildOutputProfileForPrimary(Utils.PLATFORM_PACKAGE_NAME,
822                 PrimaryDexUtils.PROFILE_PRIMARY, Process.SYSTEM_UID, Process.SYSTEM_UID,
823                 false /* isPublic */);
824 
825         List<String> dexPaths = Arrays.stream(CLASSPATHS_FOR_BOOT_IMAGE_PROFILE)
826                                         .map(envVar -> Constants.getenv(envVar))
827                                         .filter(classpath -> !TextUtils.isEmpty(classpath))
828                                         .flatMap(classpath -> Arrays.stream(classpath.split(":")))
829                                         .collect(Collectors.toList());
830 
831         var options = new MergeProfileOptions();
832         options.forceMerge = true;
833         options.forBootImage = true;
834         return mergeProfilesAndGetFd(profiles, output, dexPaths, options);
835     }
836 
837     /**
838      * Notifies ART Service that this is a boot that falls into one of the categories listed in
839      * {@link BootReason}. The current behavior is that ART Service goes through all recently used
840      * packages and dexopts those that are not dexopted. This might change in the future.
841      *
842      * This method is blocking. It takes about 30 seconds to a few minutes. During execution, {@code
843      * progressCallback} is repeatedly called whenever there is an update on the progress.
844      *
845      * See {@link #dexoptPackages} for how to customize the behavior.
846      */
847     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
onBoot(@onNull @ootReason String bootReason, @Nullable @CallbackExecutor Executor progressCallbackExecutor, @Nullable Consumer<OperationProgress> progressCallback)848     public void onBoot(@NonNull @BootReason String bootReason,
849             @Nullable @CallbackExecutor Executor progressCallbackExecutor,
850             @Nullable Consumer<OperationProgress> progressCallback) {
851         try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
852             dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor,
853                     progressCallback);
854         }
855     }
856 
857     /**
858      * Dumps the dexopt state of all packages in text format for debugging purposes.
859      *
860      * There are no stability guarantees for the output format.
861      *
862      * @throws IllegalStateException if the operation encounters an error that should never happen
863      *         (e.g., an internal logic error).
864      */
865     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
dump( @onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)866     public void dump(
867             @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
868         new DumpHelper(this).dump(pw, snapshot);
869     }
870 
871     /**
872      * Dumps the dexopt state of the given package in text format for debugging purposes.
873      *
874      * There are no stability guarantees for the output format.
875      *
876      * @throws IllegalArgumentException if the package is not found
877      * @throws IllegalStateException if the operation encounters an error that should never happen
878      *         (e.g., an internal logic error).
879      */
880     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
dumpPackage(@onNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName)881     public void dumpPackage(@NonNull PrintWriter pw,
882             @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
883         new DumpHelper(this).dumpPackage(
884                 pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
885     }
886 
887     /**
888      * Cleans up obsolete profiles and artifacts.
889      *
890      * This is done in a mark-and-sweep approach.
891      *
892      * @return The amount of the disk space freed by the cleanup, in bytes.
893      * @hide
894      */
895     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
cleanup(@onNull PackageManagerLocal.FilteredSnapshot snapshot)896     public long cleanup(@NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
897         mInjector.getDexUseManager().cleanup();
898 
899         try {
900             // For every primary dex container file or secondary dex container file of every app, if
901             // it has code, we keep the following types of files:
902             // - The reference profile and the current profiles, regardless of the hibernation state
903             //   of the app.
904             // - The dexopt artifacts, if they are up-to-date and the app is not hibernating.
905             // - Only the VDEX part of the dexopt artifacts, if the dexopt artifacts are outdated
906             //   but the VDEX part is still usable and the app is not hibernating.
907             List<ProfilePath> profilesToKeep = new ArrayList<>();
908             List<ArtifactsPath> artifactsToKeep = new ArrayList<>();
909             List<VdexPath> vdexFilesToKeep = new ArrayList<>();
910 
911             for (PackageState pkgState : snapshot.getPackageStates().values()) {
912                 if (!Utils.canDexoptPackage(pkgState, null /* appHibernationManager */)) {
913                     continue;
914                 }
915                 AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
916                 boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd());
917                 boolean keepArtifacts = !Utils.shouldSkipDexoptDueToHibernation(
918                         pkgState, mInjector.getAppHibernationManager());
919                 for (DetailedPrimaryDexInfo dexInfo :
920                         PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
921                     if (!dexInfo.hasCode()) {
922                         continue;
923                     }
924                     profilesToKeep.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
925                     profilesToKeep.addAll(PrimaryDexUtils.getCurProfiles(
926                             mInjector.getUserManager(), pkgState, dexInfo));
927                     if (keepArtifacts) {
928                         for (Abi abi : Utils.getAllAbis(pkgState)) {
929                             maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
930                                     abi, isInDalvikCache);
931                         }
932                     }
933                 }
934                 for (DetailedSecondaryDexInfo dexInfo :
935                         mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo(
936                                 pkgState.getPackageName())) {
937                     profilesToKeep.add(
938                             AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()));
939                     profilesToKeep.add(
940                             AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
941                     if (keepArtifacts) {
942                         for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
943                             maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
944                                     abi, false /* isInDalvikCache */);
945                         }
946                     }
947                 }
948             }
949             return mInjector.getArtd().cleanup(profilesToKeep, artifactsToKeep, vdexFilesToKeep);
950         } catch (RemoteException e) {
951             Utils.logArtdException(e);
952             return 0;
953         }
954     }
955 
956     /**
957      * Checks if the artifacts are up-to-date, and maybe adds them to {@code artifactsToKeep} or
958      * {@code vdexFilesToKeep} based on the result.
959      */
960     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
maybeKeepArtifacts(@onNull List<ArtifactsPath> artifactsToKeep, @NonNull List<VdexPath> vdexFilesToKeep, @NonNull PackageState pkgState, @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi, boolean isInDalvikCache)961     private void maybeKeepArtifacts(@NonNull List<ArtifactsPath> artifactsToKeep,
962             @NonNull List<VdexPath> vdexFilesToKeep, @NonNull PackageState pkgState,
963             @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi, boolean isInDalvikCache)
964             throws RemoteException {
965         try {
966             GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
967                     dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
968             if (DexFile.isValidCompilerFilter(result.compilerFilter)) {
969                 // TODO(b/263579377): This is a bit inaccurate. We may be keeping the artifacts in
970                 // dalvik-cache while OatFileAssistant actually picks the ones not in dalvik-cache.
971                 // However, this isn't a big problem because it is an edge case and it only causes
972                 // us to delete less rather than deleting more.
973                 ArtifactsPath artifacts =
974                         AidlUtils.buildArtifactsPath(dexInfo.dexPath(), abi.isa(), isInDalvikCache);
975                 if (result.compilationReason.equals(ArtConstants.REASON_VDEX)) {
976                     // Only the VDEX file is usable.
977                     vdexFilesToKeep.add(VdexPath.artifactsPath(artifacts));
978                 } else {
979                     artifactsToKeep.add(artifacts);
980                 }
981             }
982         } catch (ServiceSpecificException e) {
983             // Don't add the artifacts to the lists. They should be cleaned up.
984             Log.e(TAG,
985                     String.format("Failed to get dexopt status [packageName = %s, dexPath = %s, "
986                                     + "isa = %s, classLoaderContext = %s]",
987                             pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
988                             dexInfo.classLoaderContext()),
989                     e);
990         }
991     }
992 
993     /**
994      * Should be used by {@link BackgroundDexoptJobService} ONLY.
995      *
996      * @hide
997      */
998     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
999     @NonNull
getBackgroundDexoptJob()1000     BackgroundDexoptJob getBackgroundDexoptJob() {
1001         return mInjector.getBackgroundDexoptJob();
1002     }
1003 
1004     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
maybeDowngradePackages(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull Set<String> excludedPackages, @NonNull CancellationSignal cancellationSignal, @NonNull Executor executor)1005     private void maybeDowngradePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
1006             @NonNull Set<String> excludedPackages, @NonNull CancellationSignal cancellationSignal,
1007             @NonNull Executor executor) {
1008         if (shouldDowngrade()) {
1009             List<String> packages = getDefaultPackages(snapshot, ReasonMapping.REASON_INACTIVE)
1010                                             .stream()
1011                                             .filter(pkg -> !excludedPackages.contains(pkg))
1012                                             .collect(Collectors.toList());
1013             if (!packages.isEmpty()) {
1014                 Log.i(TAG, "Storage is low. Downgrading inactive packages");
1015                 DexoptParams params =
1016                         new DexoptParams.Builder(ReasonMapping.REASON_INACTIVE).build();
1017                 mInjector.getDexoptHelper().dexopt(snapshot, packages, params, cancellationSignal,
1018                         executor, null /* processCallbackExecutor */, null /* progressCallback */);
1019             } else {
1020                 Log.i(TAG,
1021                         "Storage is low, but downgrading is disabled or there's nothing to "
1022                                 + "downgrade");
1023             }
1024         }
1025     }
1026 
1027     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
shouldDowngrade()1028     private boolean shouldDowngrade() {
1029         try {
1030             return mInjector.getStorageManager().getAllocatableBytes(StorageManager.UUID_DEFAULT)
1031                     < DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES;
1032         } catch (IOException e) {
1033             Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
1034             return false;
1035         }
1036     }
1037 
1038     /** Returns the list of packages to process for the given reason. */
1039     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1040     @NonNull
getDefaultPackages(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String reason)1041     private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
1042             @NonNull /* @BatchDexoptReason|REASON_INACTIVE */ String reason) {
1043         var appHibernationManager = mInjector.getAppHibernationManager();
1044 
1045         // Filter out hibernating packages even if the reason is REASON_INACTIVE. This is because
1046         // artifacts for hibernating packages are already deleted.
1047         Stream<PackageState> packages = snapshot.getPackageStates().values().stream().filter(
1048                 pkgState -> Utils.canDexoptPackage(pkgState, appHibernationManager));
1049 
1050         switch (reason) {
1051             case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
1052                 packages = packages.filter(pkgState
1053                         -> mInjector.isSystemUiPackage(pkgState.getPackageName())
1054                                 || mInjector.isLauncherPackage(pkgState.getPackageName()));
1055                 break;
1056             case ReasonMapping.REASON_INACTIVE:
1057                 packages = filterAndSortByLastActiveTime(
1058                         packages, false /* keepRecent */, false /* descending */);
1059                 break;
1060             default:
1061                 // Actually, the sorting is only needed for background dexopt, but we do it for all
1062                 // cases for simplicity.
1063                 packages = filterAndSortByLastActiveTime(
1064                         packages, true /* keepRecent */, true /* descending */);
1065         }
1066 
1067         return packages.map(PackageState::getPackageName).collect(Collectors.toList());
1068     }
1069 
1070     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1071     @NonNull
filterAndSortByLastActiveTime( @onNull Stream<PackageState> packages, boolean keepRecent, boolean descending)1072     private Stream<PackageState> filterAndSortByLastActiveTime(
1073             @NonNull Stream<PackageState> packages, boolean keepRecent, boolean descending) {
1074         // "pm.dexopt.downgrade_after_inactive_days" is repurposed to also determine whether to
1075         // dexopt a package.
1076         long inactiveMs = TimeUnit.DAYS.toMillis(SystemProperties.getInt(
1077                 "pm.dexopt.downgrade_after_inactive_days", Integer.MAX_VALUE /* def */));
1078         long currentTimeMs = mInjector.getCurrentTimeMillis();
1079         long thresholdTimeMs = currentTimeMs - inactiveMs;
1080         return packages
1081                 .map(pkgState
1082                         -> Pair.create(pkgState,
1083                                 Utils.getPackageLastActiveTime(pkgState,
1084                                         mInjector.getDexUseManager(), mInjector.getUserManager())))
1085                 .filter(keepRecent ? (pair -> pair.second > thresholdTimeMs)
1086                                    : (pair -> pair.second <= thresholdTimeMs))
1087                 .sorted(descending ? Comparator.comparingLong(pair -> - pair.second)
1088                                    : Comparator.comparingLong(pair -> pair.second))
1089                 .map(pair -> pair.first);
1090     }
1091 
1092     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1093     @NonNull
mergeProfilesAndGetFd(@onNull List<ProfilePath> profiles, @NonNull OutputProfile output, @NonNull List<String> dexPaths, @NonNull MergeProfileOptions options)1094     private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles,
1095             @NonNull OutputProfile output, @NonNull List<String> dexPaths,
1096             @NonNull MergeProfileOptions options) throws SnapshotProfileException {
1097         try {
1098             boolean hasContent = false;
1099             try {
1100                 hasContent = mInjector.getArtd().mergeProfiles(
1101                         profiles, null /* referenceProfile */, output, dexPaths, options);
1102             } catch (ServiceSpecificException e) {
1103                 throw new SnapshotProfileException(e);
1104             }
1105 
1106             String path;
1107             Path emptyFile = null;
1108             if (hasContent) {
1109                 path = output.profilePath.tmpPath;
1110             } else {
1111                 // We cannot use /dev/null because `ParcelFileDescriptor` have an API `getStatSize`,
1112                 // which expects the file to be a regular file or a link, and apps may call that
1113                 // API.
1114                 emptyFile =
1115                         Files.createTempFile(Paths.get(mInjector.getTempDir()), "empty", ".tmp");
1116                 path = emptyFile.toString();
1117             }
1118             ParcelFileDescriptor fd;
1119             try {
1120                 fd = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
1121             } catch (FileNotFoundException e) {
1122                 throw new IllegalStateException(
1123                         String.format("Failed to open profile snapshot '%s'", path), e);
1124             }
1125 
1126             // The deletion is done on the open file so that only the FD keeps a reference to the
1127             // file.
1128             if (hasContent) {
1129                 mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath));
1130             } else {
1131                 Files.delete(emptyFile);
1132             }
1133 
1134             return fd;
1135         } catch (IOException | RemoteException e) {
1136             throw new SnapshotProfileException(e);
1137         }
1138     }
1139 
1140     /** @hide */
1141     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
1142     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1143     public interface BatchDexoptStartCallback {
1144         /**
1145          * Mutates {@code builder} to override the default params for {@link #dexoptPackages}. It
1146          * must ignore unknown reasons because more reasons may be added in the future.
1147          *
1148          * This is called before the start of any automatic package dexopt (i.e., not
1149          * including package dexopt initiated by the {@link #dexoptPackage} API call).
1150          *
1151          * If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the
1152          * list of packages to dexopt.
1153          *
1154          * If {@code builder.setDexoptParams} is not called, the default params built from {@code
1155          * new DexoptParams.Builder(reason)} will to used as the params for dexopting each
1156          * package.
1157          *
1158          * Additionally, {@code cancellationSignal.cancel()} can be called to cancel this operation.
1159          * If this operation is initiated by the job scheduler and the {@code reason} is {@link
1160          * ReasonMapping#REASON_BG_DEXOPT}, the job will be retried in the next <i>maintenance
1161          * window</i>. For information about <i>maintenance window</i>, see
1162          * https://developer.android.com/training/monitoring-device-state/doze-standby.
1163          *
1164          * Changing the reason is not allowed. Doing so will result in {@link IllegalStateException}
1165          * when {@link #dexoptPackages} is called.
1166          */
onBatchDexoptStart(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull @BatchDexoptReason String reason, @NonNull List<String> defaultPackages, @NonNull BatchDexoptParams.Builder builder, @NonNull CancellationSignal cancellationSignal)1167         void onBatchDexoptStart(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
1168                 @NonNull @BatchDexoptReason String reason, @NonNull List<String> defaultPackages,
1169                 @NonNull BatchDexoptParams.Builder builder,
1170                 @NonNull CancellationSignal cancellationSignal);
1171     }
1172 
1173     /** @hide */
1174     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
1175     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1176     public interface ScheduleBackgroundDexoptJobCallback {
1177         /**
1178          * Mutates {@code builder} to override the configuration of the background dexopt job.
1179          *
1180          * The default configuration described in {@link
1181          * ArtManagerLocal#scheduleBackgroundDexoptJob()} is passed to the callback as the {@code
1182          * builder} argument.
1183          *
1184          * Setting {@link JobInfo.Builder#setRequiresStorageNotLow(boolean)} is not allowed. Doing
1185          * so will result in {@link IllegalStateException} when {@link
1186          * #scheduleBackgroundDexoptJob()} is called. ART Service has its own storage check, which
1187          * skips package dexopt when the storage is low. The storage check is enabled by
1188          * default for background dexopt jobs. {@link
1189          * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} can be used to disable
1190          * the storage check by clearing the {@link ArtFlags#FLAG_SKIP_IF_STORAGE_LOW} flag.
1191          */
onOverrideJobInfo(@onNull JobInfo.Builder builder)1192         void onOverrideJobInfo(@NonNull JobInfo.Builder builder);
1193     }
1194 
1195     /** @hide */
1196     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
1197     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1198     public interface DexoptDoneCallback {
onDexoptDone(@onNull DexoptResult result)1199         void onDexoptDone(@NonNull DexoptResult result);
1200     }
1201 
1202     /**
1203      * Represents an error that happens when snapshotting profiles.
1204      *
1205      * @hide
1206      */
1207     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
1208     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1209     public static class SnapshotProfileException extends Exception {
1210         /** @hide */
SnapshotProfileException(@onNull Throwable cause)1211         public SnapshotProfileException(@NonNull Throwable cause) {
1212             super(cause);
1213         }
1214 
1215         /** @hide */
SnapshotProfileException(@onNull String message)1216         public SnapshotProfileException(@NonNull String message) {
1217             super(message);
1218         }
1219     }
1220 
1221     /**
1222      * Injector pattern for testing purpose.
1223      *
1224      * @hide
1225      */
1226     @VisibleForTesting
1227     public static class Injector {
1228         @Nullable private final ArtManagerLocal mArtManagerLocal;
1229         @Nullable private final Context mContext;
1230         @Nullable private final PackageManagerLocal mPackageManagerLocal;
1231         @Nullable private final Config mConfig;
1232         @Nullable private BackgroundDexoptJob mBgDexoptJob = null;
1233 
1234         // TODO(jiakaiz): Remove @SuppressLint and check `Build.VERSION.SDK_INT >=
1235         // Build.VERSION_CODES.UPSIDE_DOWN_CAKE` once the SDK is finalized.
1236         @SuppressLint("NewApi")
Injector(@onNull ArtManagerLocal artManagerLocal, @Nullable Context context)1237         Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) {
1238             mArtManagerLocal = artManagerLocal;
1239             mContext = context;
1240             if (context != null) {
1241                 // We only need them on Android U and above, where a context is passed.
1242                 mPackageManagerLocal = Objects.requireNonNull(
1243                         LocalManagerRegistry.getManager(PackageManagerLocal.class));
1244                 mConfig = new Config();
1245 
1246                 // Call the getters for the dependencies that aren't optional, to ensure correct
1247                 // initialization order.
1248                 getDexoptHelper();
1249                 getUserManager();
1250                 getDexUseManager();
1251                 getStorageManager();
1252                 ArtModuleServiceInitializer.getArtModuleServiceManager();
1253             } else {
1254                 mPackageManagerLocal = null;
1255                 mConfig = null;
1256             }
1257         }
1258 
1259         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1260         @NonNull
getContext()1261         public Context getContext() {
1262             return Objects.requireNonNull(mContext);
1263         }
1264 
1265         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1266         @NonNull
getPackageManagerLocal()1267         public PackageManagerLocal getPackageManagerLocal() {
1268             return Objects.requireNonNull(mPackageManagerLocal);
1269         }
1270 
1271         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1272         @NonNull
getArtd()1273         public IArtd getArtd() {
1274             return Utils.getArtd();
1275         }
1276 
1277         /** Returns a new {@link DexoptHelper} instance. */
1278         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1279         @NonNull
getDexoptHelper()1280         public DexoptHelper getDexoptHelper() {
1281             return new DexoptHelper(getContext(), getConfig());
1282         }
1283 
1284         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1285         @NonNull
getConfig()1286         public Config getConfig() {
1287             return mConfig;
1288         }
1289 
1290         /** Returns the registered {@link AppHibernationManager} instance. */
1291         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1292         @NonNull
getAppHibernationManager()1293         public AppHibernationManager getAppHibernationManager() {
1294             return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
1295         }
1296 
1297         /**
1298          * Returns the {@link BackgroundDexoptJob} instance.
1299          *
1300          * @throws RuntimeException if called during boot before the job scheduler service has
1301          *         started.
1302          */
1303         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1304         @NonNull
getBackgroundDexoptJob()1305         public synchronized BackgroundDexoptJob getBackgroundDexoptJob() {
1306             if (mBgDexoptJob == null) {
1307                 mBgDexoptJob = new BackgroundDexoptJob(mContext, mArtManagerLocal, mConfig);
1308             }
1309             return mBgDexoptJob;
1310         }
1311 
1312         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1313         @NonNull
getUserManager()1314         public UserManager getUserManager() {
1315             return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
1316         }
1317 
1318         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1319         @NonNull
getDexUseManager()1320         public DexUseManagerLocal getDexUseManager() {
1321             return Objects.requireNonNull(
1322                     LocalManagerRegistry.getManager(DexUseManagerLocal.class));
1323         }
1324 
1325         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
isSystemUiPackage(@onNull String packageName)1326         public boolean isSystemUiPackage(@NonNull String packageName) {
1327             return Utils.isSystemUiPackage(mContext, packageName);
1328         }
1329 
1330         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
isLauncherPackage(@onNull String packageName)1331         public boolean isLauncherPackage(@NonNull String packageName) {
1332             return Utils.isLauncherPackage(mContext, packageName);
1333         }
1334 
1335         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
getCurrentTimeMillis()1336         public long getCurrentTimeMillis() {
1337             return System.currentTimeMillis();
1338         }
1339 
1340         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1341         @NonNull
getStorageManager()1342         public StorageManager getStorageManager() {
1343             return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
1344         }
1345 
1346         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1347         @NonNull
getTempDir()1348         public String getTempDir() {
1349             // This is a path that system_server is known to have full access to.
1350             return "/data/system";
1351         }
1352     }
1353 }
1354