1 /*
2  * Copyright (C) 2022 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Binder;
27 import android.os.Build;
28 import android.os.Environment;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.os.ServiceSpecificException;
32 import android.os.UserHandle;
33 
34 import androidx.annotation.RequiresApi;
35 
36 import com.android.internal.annotations.GuardedBy;
37 import com.android.internal.annotations.Immutable;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.server.LocalManagerRegistry;
40 import com.android.server.art.model.DetailedDexInfo;
41 import com.android.server.art.model.DexContainerFileUseInfo;
42 import com.android.server.art.proto.DexUseProto;
43 import com.android.server.art.proto.Int32Value;
44 import com.android.server.art.proto.PackageDexUseProto;
45 import com.android.server.art.proto.PrimaryDexUseProto;
46 import com.android.server.art.proto.PrimaryDexUseRecordProto;
47 import com.android.server.art.proto.SecondaryDexUseProto;
48 import com.android.server.art.proto.SecondaryDexUseRecordProto;
49 import com.android.server.pm.PackageManagerLocal;
50 import com.android.server.pm.pkg.AndroidPackage;
51 import com.android.server.pm.pkg.AndroidPackageSplit;
52 import com.android.server.pm.pkg.PackageState;
53 
54 import com.google.auto.value.AutoValue;
55 
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileOutputStream;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.OutputStream;
62 import java.nio.file.Files;
63 import java.nio.file.Path;
64 import java.nio.file.Paths;
65 import java.nio.file.StandardCopyOption;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.Comparator;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Objects;
74 import java.util.Optional;
75 import java.util.Set;
76 import java.util.UUID;
77 import java.util.concurrent.Executors;
78 import java.util.concurrent.ScheduledExecutorService;
79 import java.util.function.BiFunction;
80 import java.util.function.Function;
81 import java.util.stream.Collectors;
82 
83 /**
84  * A singleton class that maintains the information about dex uses. This class is thread-safe.
85  *
86  * This class collects data sent directly by apps, and hence the data should be trusted as little as
87  * possible.
88  *
89  * To avoid overwriting data, {@link #load()} must be called exactly once, during initialization.
90  *
91  * @hide
92  */
93 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
94 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
95 public class DexUseManagerLocal {
96     private static final String FILENAME = "/data/system/package-dex-usage.pb";
97 
98     /**
99      * The minimum interval between disk writes.
100      *
101      * In practice, the interval will be much longer because we use a debouncer to postpone the disk
102      * write to the end of a series of changes. Note that in theory we could postpone the disk write
103      * indefinitely, and therefore we could lose data if the device isn't shut down in the normal
104      * way, but that's fine because the data isn't crucial and is recoverable.
105      *
106      * @hide
107      */
108     @VisibleForTesting public static final long INTERVAL_MS = 15_000;
109 
110     private static final Object sLock = new Object();
111 
112     // The static field is associated with the class and the class loader that loads it. In the
113     // Pre-reboot Dexopt case, this class is loaded by a separate class loader, so it doesn't share
114     // the same static field with the class outside of the class loader.
115     @GuardedBy("sLock") @Nullable private static DexUseManagerLocal sInstance = null;
116 
117     @NonNull private final Injector mInjector;
118     @NonNull private final Debouncer mDebouncer;
119 
120     private final Object mLock = new Object();
121     @GuardedBy("mLock") @NonNull private DexUse mDexUse; // Initialized by `load`.
122     @GuardedBy("mLock") private int mRevision = 0;
123     @GuardedBy("mLock") private int mLastCommittedRevision = 0;
124     @GuardedBy("mLock")
125     @NonNull
126     private SecondaryDexLocationManager mSecondaryDexLocationManager =
127             new SecondaryDexLocationManager();
128 
129     /**
130      * Creates the singleton instance.
131      *
132      * Only {@code SystemServer} should create the instance and register it in {@link
133      * LocalManagerRegistry}. Other API users should obtain the instance from {@link
134      * LocalManagerRegistry}.
135      *
136      * In practice, it must be created and registered in {@link LocalManagerRegistry} before {@code
137      * PackageManagerService} starts because {@code PackageManagerService} needs it as soon as it
138      * starts. It's safe to create an instance early because it doesn't depend on anything else.
139      *
140      * @param context the system server context
141      * @throws IllegalStateException if the instance is already created
142      * @throws NullPointerException if required dependencies are missing
143      */
144     @NonNull
createInstance(@onNull Context context)145     public static DexUseManagerLocal createInstance(@NonNull Context context) {
146         synchronized (sLock) {
147             if (sInstance != null) {
148                 throw new IllegalStateException("DexUseManagerLocal is already created");
149             }
150             sInstance = new DexUseManagerLocal(context);
151             return sInstance;
152         }
153     }
154 
DexUseManagerLocal(@onNull Context context)155     private DexUseManagerLocal(@NonNull Context context) {
156         this(new Injector(context));
157     }
158 
159     /** @hide */
160     @VisibleForTesting
DexUseManagerLocal(@onNull Injector injector)161     public DexUseManagerLocal(@NonNull Injector injector) {
162         mInjector = injector;
163         mDebouncer = new Debouncer(INTERVAL_MS, mInjector::createScheduledExecutor);
164         load();
165     }
166 
167     /** Notifies dex use manager that {@link Context#registerReceiver} is ready for use. */
systemReady()168     public void systemReady() {
169         Utils.check(!mInjector.isPreReboot());
170         mInjector.getArtManagerLocal().systemReady();
171         // Save the data when the device is being shut down. The receiver is blocking, with a
172         // 10s timeout.
173         mInjector.getContext().registerReceiver(new BroadcastReceiver() {
174             @Override
175             public void onReceive(Context context, Intent intent) {
176                 context.unregisterReceiver(this);
177                 save();
178             }
179         }, new IntentFilter(Intent.ACTION_SHUTDOWN));
180     }
181 
182     /**
183      * Returns the information about the use of all secondary dex files owned by the given package,
184      * or an empty list if the package does not own any secondary dex file or it does not exist.
185      */
186     @NonNull
getSecondaryDexContainerFileUseInfo( @onNull String packageName)187     public List<DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo(
188             @NonNull String packageName) {
189         return getSecondaryDexInfo(packageName)
190                 .stream()
191                 .map(info
192                         -> DexContainerFileUseInfo.create(info.dexPath(), info.userHandle(),
193                                 info.loaders()
194                                         .stream()
195                                         .map(loader -> loader.loadingPackageName())
196                                         .collect(Collectors.toSet())))
197                 .collect(Collectors.toList());
198     }
199 
200     /**
201      * Returns all entities that load the given primary dex file owned by the given package.
202      *
203      * @hide
204      */
205     @NonNull
getPrimaryDexLoaders( @onNull String packageName, @NonNull String dexPath)206     public Set<DexLoader> getPrimaryDexLoaders(
207             @NonNull String packageName, @NonNull String dexPath) {
208         synchronized (mLock) {
209             PackageDexUse packageDexUse =
210                     mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
211             if (packageDexUse == null) {
212                 return Set.of();
213             }
214             PrimaryDexUse primaryDexUse = packageDexUse.mPrimaryDexUseByDexFile.get(dexPath);
215             if (primaryDexUse == null) {
216                 return Set.of();
217             }
218             return Set.copyOf(primaryDexUse.mRecordByLoader.keySet());
219         }
220     }
221 
222     /**
223      * Returns whether a primary dex file owned by the given package is used by other apps.
224      *
225      * @hide
226      */
isPrimaryDexUsedByOtherApps( @onNull String packageName, @NonNull String dexPath)227     public boolean isPrimaryDexUsedByOtherApps(
228             @NonNull String packageName, @NonNull String dexPath) {
229         return isUsedByOtherApps(getPrimaryDexLoaders(packageName, dexPath), packageName);
230     }
231 
232     /**
233      * Returns the basic information about all secondary dex files owned by the given package. This
234      * method doesn't take dex file visibility into account, so it can only be used for debugging
235      * purpose, such as dumpsys.
236      *
237      * @see #getCheckedSecondaryDexInfo(String)
238      * @hide
239      */
getSecondaryDexInfo( @onNull String packageName)240     public @NonNull List<? extends SecondaryDexInfo> getSecondaryDexInfo(
241             @NonNull String packageName) {
242         return getSecondaryDexInfoImpl(
243                 packageName, false /* checkDexFile */, false /* excludeObsoleteDexesAndLoaders */);
244     }
245 
246     /**
247      * Same as above, but requires disk IO, and returns the detailed information, including dex file
248      * visibility.
249      *
250      * @param excludeObsoleteDexesAndLoaders If true, excludes secondary dex files and loaders based
251      *         on file visibility. More specifically, excludes loaders that can no longer load a
252      *         secondary dex file due to a file visibility change, and excludes secondary dex files
253      *         that are not found or only have obsolete loaders
254      *
255      * @hide
256      */
getCheckedSecondaryDexInfo( @onNull String packageName, boolean excludeObsoleteDexesAndLoaders)257     public @NonNull List<CheckedSecondaryDexInfo> getCheckedSecondaryDexInfo(
258             @NonNull String packageName, boolean excludeObsoleteDexesAndLoaders) {
259         return getSecondaryDexInfoImpl(
260                 packageName, true /* checkDexFile */, excludeObsoleteDexesAndLoaders);
261     }
262 
263     /**
264      * Returns the last time the package was used, or 0 if the package has never been used.
265      *
266      * @hide
267      */
getPackageLastUsedAtMs(@onNull String packageName)268     public long getPackageLastUsedAtMs(@NonNull String packageName) {
269         synchronized (mLock) {
270             PackageDexUse packageDexUse =
271                     mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
272             if (packageDexUse == null) {
273                 return 0;
274             }
275             long primaryLastUsedAtMs =
276                     packageDexUse.mPrimaryDexUseByDexFile.values()
277                             .stream()
278                             .flatMap(primaryDexUse
279                                     -> primaryDexUse.mRecordByLoader.values().stream())
280                             .map(record -> record.mLastUsedAtMs)
281                             .max(Long::compare)
282                             .orElse(0l);
283             long secondaryLastUsedAtMs =
284                     packageDexUse.mSecondaryDexUseByDexFile.values()
285                             .stream()
286                             .flatMap(secondaryDexUse
287                                     -> secondaryDexUse.mRecordByLoader.values().stream())
288                             .map(record -> record.mLastUsedAtMs)
289                             .max(Long::compare)
290                             .orElse(0l);
291             return Math.max(primaryLastUsedAtMs, secondaryLastUsedAtMs);
292         }
293     }
294 
295     /**
296      * @param checkDexFile if true, check the existence and visibility of the dex files. Note that
297      *         the value of the {@link CheckedSecondaryDexInfo#fileVisibility()} field is undefined
298      *         if this argument is false
299      * @param excludeObsoleteDexesAndLoaders see {@link #getCheckedSecondaryDexInfo}. Only takes
300      *         effect if {@code checkDexFile} is true
301      */
getSecondaryDexInfoImpl( @onNull String packageName, boolean checkDexFile, boolean excludeObsoleteDexesAndLoaders)302     private @NonNull List<CheckedSecondaryDexInfo> getSecondaryDexInfoImpl(
303             @NonNull String packageName, boolean checkDexFile,
304             boolean excludeObsoleteDexesAndLoaders) {
305         synchronized (mLock) {
306             PackageDexUse packageDexUse =
307                     mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
308             if (packageDexUse == null) {
309                 return List.of();
310             }
311             var results = new ArrayList<CheckedSecondaryDexInfo>();
312             for (var entry : packageDexUse.mSecondaryDexUseByDexFile.entrySet()) {
313                 String dexPath = entry.getKey();
314                 SecondaryDexUse secondaryDexUse = entry.getValue();
315 
316                 @FileVisibility
317                 int visibility = checkDexFile ? getDexFileVisibility(dexPath)
318                                               : FileVisibility.OTHER_READABLE;
319                 if (visibility == FileVisibility.NOT_FOUND && excludeObsoleteDexesAndLoaders) {
320                     continue;
321                 }
322 
323                 Map<DexLoader, SecondaryDexUseRecord> filteredRecordByLoader;
324                 if (visibility == FileVisibility.OTHER_READABLE
325                         || !excludeObsoleteDexesAndLoaders) {
326                     filteredRecordByLoader = secondaryDexUse.mRecordByLoader;
327                 } else {
328                     // Only keep the entry that belongs to the same app.
329                     DexLoader sameApp = DexLoader.create(packageName, false /* isolatedProcess */);
330                     SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.get(sameApp);
331                     filteredRecordByLoader = record != null ? Map.of(sameApp, record) : Map.of();
332                 }
333                 if (filteredRecordByLoader.isEmpty()) {
334                     continue;
335                 }
336                 List<String> distinctClcList =
337                         filteredRecordByLoader.values()
338                                 .stream()
339                                 .map(record -> Utils.assertNonEmpty(record.mClassLoaderContext))
340                                 .filter(clc
341                                         -> !clc.equals(
342                                                 SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT))
343                                 .distinct()
344                                 .collect(Collectors.toList());
345                 String clc;
346                 if (distinctClcList.size() == 0) {
347                     clc = SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT;
348                 } else if (distinctClcList.size() == 1) {
349                     clc = distinctClcList.get(0);
350                 } else {
351                     // If there are more than one class loader contexts, we can't dexopt the dex
352                     // file.
353                     clc = SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS;
354                 }
355                 // Although we filter out unsupported CLCs above, `distinctAbiNames` and `loaders`
356                 // still need to take apps with unsupported CLCs into account because the vdex file
357                 // is still usable to them.
358                 Set<String> distinctAbiNames =
359                         filteredRecordByLoader.values()
360                                 .stream()
361                                 .map(record -> Utils.assertNonEmpty(record.mAbiName))
362                                 .collect(Collectors.toSet());
363                 Set<DexLoader> loaders = Set.copyOf(filteredRecordByLoader.keySet());
364                 results.add(CheckedSecondaryDexInfo.create(dexPath,
365                         Objects.requireNonNull(secondaryDexUse.mUserHandle), clc, distinctAbiNames,
366                         loaders, isUsedByOtherApps(loaders, packageName), visibility));
367             }
368             return Collections.unmodifiableList(results);
369         }
370     }
371 
372     /**
373      * Notifies ART Service that a list of dex container files have been loaded.
374      *
375      * ART Service uses this information to:
376      * <ul>
377      *   <li>Determine whether an app is used by another app
378      *   <li>Record which secondary dex container files to dexopt and how to dexopt them
379      * </ul>
380      *
381      * @param loadingPackageName the name of the package who performs the load. ART Service assumes
382      *         that this argument has been validated that it exists in the snapshot and matches the
383      *         calling UID
384      * @param classLoaderContextByDexContainerFile a map from dex container files' absolute paths to
385      *         the string representations of the class loader contexts used to load them
386      * @throws IllegalArgumentException if {@code classLoaderContextByDexContainerFile} contains
387      *         invalid entries
388      */
notifyDexContainersLoaded(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String loadingPackageName, @NonNull Map<String, String> classLoaderContextByDexContainerFile)389     public void notifyDexContainersLoaded(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
390             @NonNull String loadingPackageName,
391             @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
392         // "android" comes from `SystemServerDexLoadReporter`. ART Services doesn't need to handle
393         // this case because it doesn't compile system server and system server isn't allowed to
394         // load artifacts produced by ART Services.
395         if (loadingPackageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
396             return;
397         }
398 
399         validateInputs(snapshot, loadingPackageName, classLoaderContextByDexContainerFile);
400 
401         // TODO(jiakaiz): Investigate whether it should also be considered as isolated process if
402         // `Process.isSdkSandboxUid` returns true.
403         boolean isolatedProcess = Process.isIsolatedUid(Binder.getCallingUid());
404         long lastUsedAtMs = mInjector.getCurrentTimeMillis();
405 
406         for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
407             String dexPath = Utils.assertNonEmpty(entry.getKey());
408             String classLoaderContext = Utils.assertNonEmpty(entry.getValue());
409             String owningPackageName = findOwningPackage(snapshot, loadingPackageName,
410                     (pkgState) -> isOwningPackageForPrimaryDex(pkgState, dexPath));
411             if (owningPackageName != null) {
412                 addPrimaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
413                         lastUsedAtMs);
414                 continue;
415             }
416             Path path = Paths.get(dexPath);
417             synchronized (mLock) {
418                 owningPackageName = findOwningPackage(snapshot, loadingPackageName,
419                         (pkgState) -> isOwningPackageForSecondaryDexLocked(pkgState, path));
420             }
421             if (owningPackageName != null) {
422                 PackageState loadingPkgState =
423                         Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
424                 // An app is always launched with its primary ABI.
425                 Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState);
426                 addSecondaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
427                         classLoaderContext, abi.name(), lastUsedAtMs);
428                 continue;
429             }
430             // It is expected that a dex file isn't owned by any package. For example, the dex
431             // file could be a shared library jar.
432         }
433     }
434 
435     @Nullable
findOwningPackage(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String loadingPackageName, @NonNull Function<PackageState, Boolean> predicate)436     private static String findOwningPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
437             @NonNull String loadingPackageName,
438             @NonNull Function<PackageState, Boolean> predicate) {
439         // Most likely, the package is loading its own dex file, so we check this first as an
440         // optimization.
441         PackageState loadingPkgState = Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
442         if (predicate.apply(loadingPkgState)) {
443             return loadingPkgState.getPackageName();
444         }
445 
446         for (PackageState pkgState : snapshot.getPackageStates().values()) {
447             if (predicate.apply(pkgState)) {
448                 return pkgState.getPackageName();
449             }
450         }
451 
452         return null;
453     }
454 
isOwningPackageForPrimaryDex( @onNull PackageState pkgState, @NonNull String dexPath)455     private static boolean isOwningPackageForPrimaryDex(
456             @NonNull PackageState pkgState, @NonNull String dexPath) {
457         AndroidPackage pkg = pkgState.getAndroidPackage();
458         if (pkg == null) {
459             return false;
460         }
461         List<AndroidPackageSplit> splits = pkg.getSplits();
462         for (int i = 0; i < splits.size(); i++) {
463             if (splits.get(i).getPath().equals(dexPath)) {
464                 return true;
465             }
466         }
467         return false;
468     }
469 
470     @GuardedBy("mLock")
isOwningPackageForSecondaryDexLocked( @onNull PackageState pkgState, @NonNull Path dexPath)471     private boolean isOwningPackageForSecondaryDexLocked(
472             @NonNull PackageState pkgState, @NonNull Path dexPath) {
473         UserHandle userHandle = Binder.getCallingUserHandle();
474         List<Path> locations = mSecondaryDexLocationManager.getLocations(pkgState, userHandle);
475         for (int i = 0; i < locations.size(); i++) {
476             if (dexPath.startsWith(locations.get(i))) {
477                 return true;
478             }
479         }
480         return false;
481     }
482 
addPrimaryDexUse(@onNull String owningPackageName, @NonNull String dexPath, @NonNull String loadingPackageName, boolean isolatedProcess, long lastUsedAtMs)483     private void addPrimaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
484             @NonNull String loadingPackageName, boolean isolatedProcess, long lastUsedAtMs) {
485         synchronized (mLock) {
486             PrimaryDexUseRecord record =
487                     mDexUse.mPackageDexUseByOwningPackageName
488                             .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
489                             .mPrimaryDexUseByDexFile
490                             .computeIfAbsent(dexPath, k -> new PrimaryDexUse())
491                             .mRecordByLoader.computeIfAbsent(
492                                     DexLoader.create(loadingPackageName, isolatedProcess),
493                                     k -> new PrimaryDexUseRecord());
494             record.mLastUsedAtMs = lastUsedAtMs;
495             mRevision++;
496         }
497         maybeSaveAsync();
498     }
499 
addSecondaryDexUse(@onNull String owningPackageName, @NonNull String dexPath, @NonNull String loadingPackageName, boolean isolatedProcess, @NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs)500     private void addSecondaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
501             @NonNull String loadingPackageName, boolean isolatedProcess,
502             @NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) {
503         synchronized (mLock) {
504             SecondaryDexUse secondaryDexUse =
505                     mDexUse.mPackageDexUseByOwningPackageName
506                             .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
507                             .mSecondaryDexUseByDexFile.computeIfAbsent(
508                                     dexPath, k -> new SecondaryDexUse());
509             secondaryDexUse.mUserHandle = Binder.getCallingUserHandle();
510             SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.computeIfAbsent(
511                     DexLoader.create(loadingPackageName, isolatedProcess),
512                     k -> new SecondaryDexUseRecord());
513             record.mClassLoaderContext = classLoaderContext;
514             record.mAbiName = abiName;
515             record.mLastUsedAtMs = lastUsedAtMs;
516             mRevision++;
517         }
518         maybeSaveAsync();
519     }
520 
521     /** @hide */
dump()522     public @NonNull String dump() {
523         var builder = DexUseProto.newBuilder();
524         synchronized (mLock) {
525             mDexUse.toProto(builder);
526         }
527         return builder.build().toString();
528     }
529 
save()530     private void save() {
531         Utils.check(!mInjector.isPreReboot());
532         var builder = DexUseProto.newBuilder();
533         int thisRevision;
534         synchronized (mLock) {
535             if (mRevision <= mLastCommittedRevision) {
536                 return;
537             }
538             mDexUse.toProto(builder);
539             thisRevision = mRevision;
540         }
541         var file = new File(mInjector.getFilename());
542         File tempFile = null;
543         try {
544             tempFile = File.createTempFile(file.getName(), null /* suffix */, file.getParentFile());
545             try (OutputStream out = new FileOutputStream(tempFile.getPath())) {
546                 builder.build().writeTo(out);
547             }
548             synchronized (mLock) {
549                 // Check revision again in case `mLastCommittedRevision` has changed since the check
550                 // above, to avoid ABA race.
551                 if (thisRevision > mLastCommittedRevision) {
552                     Files.move(tempFile.toPath(), file.toPath(),
553                             StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
554                     mLastCommittedRevision = thisRevision;
555                 }
556             }
557         } catch (IOException e) {
558             AsLog.e("Failed to save dex use data", e);
559         } finally {
560             Utils.deleteIfExistsSafe(tempFile);
561         }
562     }
563 
maybeSaveAsync()564     private void maybeSaveAsync() {
565         Utils.check(!mInjector.isPreReboot());
566         mDebouncer.maybeRunAsync(this::save);
567     }
568 
569     /** This should only be called during initialization. */
load()570     private void load() {
571         DexUseProto proto = null;
572         try (InputStream in = new FileInputStream(mInjector.getFilename())) {
573             proto = DexUseProto.parseFrom(in);
574         } catch (IOException e) {
575             // Nothing else we can do but to start from scratch.
576             AsLog.e("Failed to load dex use data", e);
577         }
578         synchronized (mLock) {
579             if (mDexUse != null) {
580                 throw new IllegalStateException("Load has already been attempted");
581             }
582             mDexUse = new DexUse();
583             if (proto != null) {
584                 mDexUse.fromProto(
585                         proto, ArtJni::validateDexPath, ArtJni::validateClassLoaderContext);
586             }
587         }
588     }
589 
isUsedByOtherApps( @onNull Set<DexLoader> loaders, @NonNull String owningPackageName)590     private static boolean isUsedByOtherApps(
591             @NonNull Set<DexLoader> loaders, @NonNull String owningPackageName) {
592         return loaders.stream().anyMatch(loader -> isLoaderOtherApp(loader, owningPackageName));
593     }
594 
595     /**
596      * Returns true if {@code loader} is considered as "other app" (i.e., its process UID is
597      * different from the UID of the package represented by {@code owningPackageName}).
598      *
599      * @hide
600      */
isLoaderOtherApp( @onNull DexLoader loader, @NonNull String owningPackageName)601     public static boolean isLoaderOtherApp(
602             @NonNull DexLoader loader, @NonNull String owningPackageName) {
603         // If the dex file is loaded by an isolated process of the same app, it can also be
604         // considered as "used by other apps" because isolated processes are sandboxed and can only
605         // read world readable files, so they need the dexopt artifacts to be world readable. An
606         // example of such a package is webview.
607         return !loader.loadingPackageName().equals(owningPackageName) || loader.isolatedProcess();
608     }
609 
validateInputs(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String loadingPackageName, @NonNull Map<String, String> classLoaderContextByDexContainerFile)610     private void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
611             @NonNull String loadingPackageName,
612             @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
613         if (classLoaderContextByDexContainerFile.isEmpty()) {
614             throw new IllegalArgumentException("Nothing to record");
615         }
616 
617         for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
618             Utils.assertNonEmpty(entry.getKey());
619             String errorMsg = ArtJni.validateDexPath(entry.getKey());
620             if (errorMsg != null) {
621                 throw new IllegalArgumentException(errorMsg);
622             }
623             Utils.assertNonEmpty(entry.getValue());
624             errorMsg = ArtJni.validateClassLoaderContext(entry.getKey(), entry.getValue());
625             if (errorMsg != null) {
626                 throw new IllegalArgumentException(errorMsg);
627             }
628         }
629 
630         // TODO(b/253570365): Make the validation more strict.
631     }
632 
getDexFileVisibility(@onNull String dexPath)633     private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) {
634         try {
635             return mInjector.getArtd().getDexFileVisibility(dexPath);
636         } catch (ServiceSpecificException | RemoteException e) {
637             AsLog.e("Failed to get visibility of " + dexPath, e);
638             return FileVisibility.NOT_FOUND;
639         }
640     }
641 
642     /** @hide */
643     @Nullable
getSecondaryClassLoaderContext( @onNull String owningPackageName, @NonNull String dexFile, @NonNull DexLoader loader)644     public String getSecondaryClassLoaderContext(
645             @NonNull String owningPackageName, @NonNull String dexFile, @NonNull DexLoader loader) {
646         synchronized (mLock) {
647             return Optional
648                     .ofNullable(mDexUse.mPackageDexUseByOwningPackageName.get(owningPackageName))
649                     .map(packageDexUse -> packageDexUse.mSecondaryDexUseByDexFile.get(dexFile))
650                     .map(secondaryDexUse -> secondaryDexUse.mRecordByLoader.get(loader))
651                     .map(record -> record.mClassLoaderContext)
652                     .orElse(null);
653         }
654     }
655 
656     /**
657      * Cleans up obsolete information about dex files and packages that no longer exist.
658      *
659      * @hide
660      */
cleanup()661     public void cleanup() {
662         Set<String> packageNames = mInjector.getAllPackageNames();
663         Map<String, Integer> dexFileVisibilityByName = new HashMap<>();
664 
665         // Scan the data in two passes to avoid holding the lock during I/O.
666         synchronized (mLock) {
667             for (PackageDexUse packageDexUse : mDexUse.mPackageDexUseByOwningPackageName.values()) {
668                 for (String dexFile : packageDexUse.mPrimaryDexUseByDexFile.keySet()) {
669                     dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND);
670                 }
671                 for (String dexFile : packageDexUse.mSecondaryDexUseByDexFile.keySet()) {
672                     dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND);
673                 }
674             }
675         }
676 
677         for (var entry : dexFileVisibilityByName.entrySet()) {
678             entry.setValue(getDexFileVisibility(entry.getKey()));
679         }
680 
681         synchronized (mLock) {
682             for (var it = mDexUse.mPackageDexUseByOwningPackageName.entrySet().iterator();
683                     it.hasNext();) {
684                 Map.Entry<String, PackageDexUse> entry = it.next();
685                 String owningPackageName = entry.getKey();
686                 PackageDexUse packageDexUse = entry.getValue();
687 
688                 if (!packageNames.contains(owningPackageName)) {
689                     // Remove information about the non-existing owning package.
690                     it.remove();
691                     mRevision++;
692                     continue;
693                 }
694 
695                 cleanupPrimaryDexUsesLocked(packageDexUse.mPrimaryDexUseByDexFile, packageNames,
696                         dexFileVisibilityByName, owningPackageName);
697 
698                 cleanupSecondaryDexUsesLocked(packageDexUse.mSecondaryDexUseByDexFile, packageNames,
699                         dexFileVisibilityByName, owningPackageName);
700 
701                 if (packageDexUse.mPrimaryDexUseByDexFile.isEmpty()
702                         && packageDexUse.mSecondaryDexUseByDexFile.isEmpty()) {
703                     it.remove();
704                     mRevision++;
705                 }
706             }
707         }
708 
709         maybeSaveAsync();
710     }
711 
712     @GuardedBy("mLock")
cleanupPrimaryDexUsesLocked(@onNull Map<String, PrimaryDexUse> primaryDexUses, @NonNull Set<String> packageNames, @NonNull Map<String, Integer> dexFileVisibilityByName, @NonNull String owningPackageName)713     private void cleanupPrimaryDexUsesLocked(@NonNull Map<String, PrimaryDexUse> primaryDexUses,
714             @NonNull Set<String> packageNames,
715             @NonNull Map<String, Integer> dexFileVisibilityByName,
716             @NonNull String owningPackageName) {
717         for (var it = primaryDexUses.entrySet().iterator(); it.hasNext();) {
718             Map.Entry<String, PrimaryDexUse> entry = it.next();
719             String dexFile = entry.getKey();
720             PrimaryDexUse primaryDexUse = entry.getValue();
721 
722             if (!dexFileVisibilityByName.containsKey(dexFile)) {
723                 // This can only happen when the file is added after the first pass. We can just
724                 // keep it as-is and check it in the next `cleanup` run.
725                 continue;
726             }
727 
728             @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile);
729 
730             if (visibility == FileVisibility.NOT_FOUND) {
731                 // Remove information about the non-existing dex files.
732                 it.remove();
733                 mRevision++;
734                 continue;
735             }
736 
737             cleanupRecordsLocked(
738                     primaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName);
739 
740             if (primaryDexUse.mRecordByLoader.isEmpty()) {
741                 it.remove();
742                 mRevision++;
743             }
744         }
745     }
746 
747     @GuardedBy("mLock")
cleanupSecondaryDexUsesLocked( @onNull Map<String, SecondaryDexUse> secondaryDexUses, @NonNull Set<String> packageNames, @NonNull Map<String, Integer> dexFileVisibilityByName, @NonNull String owningPackageName)748     private void cleanupSecondaryDexUsesLocked(
749             @NonNull Map<String, SecondaryDexUse> secondaryDexUses,
750             @NonNull Set<String> packageNames,
751             @NonNull Map<String, Integer> dexFileVisibilityByName,
752             @NonNull String owningPackageName) {
753         for (var it = secondaryDexUses.entrySet().iterator(); it.hasNext();) {
754             Map.Entry<String, SecondaryDexUse> entry = it.next();
755             String dexFile = entry.getKey();
756             SecondaryDexUse secondaryDexUse = entry.getValue();
757 
758             if (!dexFileVisibilityByName.containsKey(dexFile)) {
759                 // This can only happen when the file is added after the first pass. We can just
760                 // keep it as-is and check it in the next `cleanup` run.
761                 continue;
762             }
763 
764             @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile);
765 
766             // Remove information about non-existing dex files.
767             if (visibility == FileVisibility.NOT_FOUND) {
768                 it.remove();
769                 mRevision++;
770                 continue;
771             }
772 
773             cleanupRecordsLocked(
774                     secondaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName);
775 
776             if (secondaryDexUse.mRecordByLoader.isEmpty()) {
777                 it.remove();
778                 mRevision++;
779             }
780         }
781     }
782 
783     @GuardedBy("mLock")
cleanupRecordsLocked(@onNull Map<DexLoader, ?> records, @NonNull Set<String> packageNames, @FileVisibility int visibility, @NonNull String owningPackageName)784     private void cleanupRecordsLocked(@NonNull Map<DexLoader, ?> records,
785             @NonNull Set<String> packageNames, @FileVisibility int visibility,
786             @NonNull String owningPackageName) {
787         for (var it = records.entrySet().iterator(); it.hasNext();) {
788             Map.Entry<DexLoader, ?> entry = it.next();
789             DexLoader loader = entry.getKey();
790 
791             if (!packageNames.contains(loader.loadingPackageName())) {
792                 // Remove information about the non-existing loading package.
793                 it.remove();
794                 mRevision++;
795                 continue;
796             }
797 
798             if (visibility == FileVisibility.NOT_OTHER_READABLE
799                     && isLoaderOtherApp(loader, owningPackageName)) {
800                 // The visibility must have changed since the last load. The loader cannot load this
801                 // dex file anymore.
802                 it.remove();
803                 mRevision++;
804                 continue;
805             }
806         }
807     }
808 
809     /**
810      * Basic information about a secondary dex file (an APK or JAR file that an app adds to its
811      * own data directory and loads dynamically).
812      *
813      * @hide
814      */
815     @Immutable
816     public abstract static class SecondaryDexInfo implements DetailedDexInfo {
817         // Special encoding used to denote a foreign ClassLoader was found when trying to encode
818         // class loader contexts for each classpath element in a ClassLoader.
819         // Must be in sync with `kUnsupportedClassLoaderContextEncoding` in
820         // `art/runtime/class_loader_context.h`.
821         public static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
822                 "=UnsupportedClassLoaderContext=";
823 
824         // Special encoding used to denote that a dex file is loaded by different packages with
825         // different ClassLoader's. Only for display purpose (e.g., in dumpsys). This value is not
826         // written to the file, and so far only used here.
827         @VisibleForTesting
828         public static final String VARYING_CLASS_LOADER_CONTEXTS = "=VaryingClassLoaderContexts=";
829 
830         /** The absolute path to the dex file within the user's app data directory. */
dexPath()831         public abstract @NonNull String dexPath();
832 
833         /**
834          * The {@link UserHandle} that represents the human user who owns and loads the dex file. A
835          * secondary dex file belongs to a specific human user, and only that user can load it.
836          */
userHandle()837         public abstract @NonNull UserHandle userHandle();
838 
839         /**
840          * A string describing the structure of the class loader that the dex file is loaded with,
841          * or {@link #UNSUPPORTED_CLASS_LOADER_CONTEXT} or {@link #VARYING_CLASS_LOADER_CONTEXTS}.
842          */
displayClassLoaderContext()843         public abstract @NonNull String displayClassLoaderContext();
844 
845         /**
846          * A string describing the structure of the class loader that the dex file is loaded with,
847          * or null if the class loader context is invalid.
848          */
classLoaderContext()849         public @Nullable String classLoaderContext() {
850             return !displayClassLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)
851                             && !displayClassLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS)
852                     ? displayClassLoaderContext()
853                     : null;
854         }
855 
856         /** The set of ABIs of the dex file is loaded with. Guaranteed to be non-empty. */
abiNames()857         public abstract @NonNull Set<String> abiNames();
858 
859         /** The set of entities that load the dex file. Guaranteed to be non-empty. */
loaders()860         public abstract @NonNull Set<DexLoader> loaders();
861 
862         /** Returns whether the dex file is used by apps other than the app that owns it. */
isUsedByOtherApps()863         public abstract boolean isUsedByOtherApps();
864     }
865 
866     /**
867      * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its
868      * own data directory and loads dynamically). It contains the visibility of the dex file in
869      * addition to what is in {@link SecondaryDexInfo}, but producing it requires disk IO.
870      *
871      * @hide
872      */
873     @Immutable
874     @AutoValue
875     public abstract static class CheckedSecondaryDexInfo
876             extends SecondaryDexInfo implements DetailedDexInfo {
create(@onNull String dexPath, @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext, @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders, boolean isUsedByOtherApps, @FileVisibility int fileVisibility)877         static CheckedSecondaryDexInfo create(@NonNull String dexPath,
878                 @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext,
879                 @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders,
880                 boolean isUsedByOtherApps, @FileVisibility int fileVisibility) {
881             return new AutoValue_DexUseManagerLocal_CheckedSecondaryDexInfo(dexPath, userHandle,
882                     displayClassLoaderContext, Collections.unmodifiableSet(abiNames),
883                     Collections.unmodifiableSet(loaders), isUsedByOtherApps, fileVisibility);
884         }
885 
886         /** Indicates the visibility of the dex file. */
fileVisibility()887         public abstract @FileVisibility int fileVisibility();
888     }
889 
890     private static class DexUse {
891         @NonNull Map<String, PackageDexUse> mPackageDexUseByOwningPackageName = new HashMap<>();
892 
toProto(@onNull DexUseProto.Builder builder)893         void toProto(@NonNull DexUseProto.Builder builder) {
894             for (var entry : mPackageDexUseByOwningPackageName.entrySet()) {
895                 var packageBuilder =
896                         PackageDexUseProto.newBuilder().setOwningPackageName(entry.getKey());
897                 entry.getValue().toProto(packageBuilder);
898                 builder.addPackageDexUse(packageBuilder);
899             }
900         }
901 
fromProto(@onNull DexUseProto proto, @NonNull Function<String, String> validateDexPath, @NonNull BiFunction<String, String, String> validateClassLoaderContext)902         void fromProto(@NonNull DexUseProto proto,
903                 @NonNull Function<String, String> validateDexPath,
904                 @NonNull BiFunction<String, String, String> validateClassLoaderContext) {
905             for (PackageDexUseProto packageProto : proto.getPackageDexUseList()) {
906                 var packageDexUse = new PackageDexUse();
907                 packageDexUse.fromProto(packageProto, validateDexPath, validateClassLoaderContext);
908                 mPackageDexUseByOwningPackageName.put(
909                         Utils.assertNonEmpty(packageProto.getOwningPackageName()), packageDexUse);
910             }
911         }
912     }
913 
914     private static class PackageDexUse {
915         /**
916          * The keys are absolute paths to primary dex files of the owning package (the base APK and
917          * split APKs).
918          */
919         @NonNull Map<String, PrimaryDexUse> mPrimaryDexUseByDexFile = new HashMap<>();
920 
921         /**
922          * The keys are absolute paths to secondary dex files of the owning package (the APKs and
923          * JARs in CE and DE directories).
924          */
925         @NonNull Map<String, SecondaryDexUse> mSecondaryDexUseByDexFile = new HashMap<>();
926 
toProto(@onNull PackageDexUseProto.Builder builder)927         void toProto(@NonNull PackageDexUseProto.Builder builder) {
928             for (var entry : mPrimaryDexUseByDexFile.entrySet()) {
929                 var primaryBuilder = PrimaryDexUseProto.newBuilder().setDexFile(entry.getKey());
930                 entry.getValue().toProto(primaryBuilder);
931                 builder.addPrimaryDexUse(primaryBuilder);
932             }
933             for (var entry : mSecondaryDexUseByDexFile.entrySet()) {
934                 var secondaryBuilder = SecondaryDexUseProto.newBuilder().setDexFile(entry.getKey());
935                 entry.getValue().toProto(secondaryBuilder);
936                 builder.addSecondaryDexUse(secondaryBuilder);
937             }
938         }
939 
fromProto(@onNull PackageDexUseProto proto, @NonNull Function<String, String> validateDexPath, @NonNull BiFunction<String, String, String> validateClassLoaderContext)940         void fromProto(@NonNull PackageDexUseProto proto,
941                 @NonNull Function<String, String> validateDexPath,
942                 @NonNull BiFunction<String, String, String> validateClassLoaderContext) {
943             for (PrimaryDexUseProto primaryProto : proto.getPrimaryDexUseList()) {
944                 var primaryDexUse = new PrimaryDexUse();
945                 primaryDexUse.fromProto(primaryProto);
946                 mPrimaryDexUseByDexFile.put(
947                         Utils.assertNonEmpty(primaryProto.getDexFile()), primaryDexUse);
948             }
949             for (SecondaryDexUseProto secondaryProto : proto.getSecondaryDexUseList()) {
950                 String dexFile = Utils.assertNonEmpty(secondaryProto.getDexFile());
951 
952                 // Skip invalid dex paths persisted by previous versions.
953                 String errorMsg = validateDexPath.apply(dexFile);
954                 if (errorMsg != null) {
955                     AsLog.e(errorMsg);
956                     continue;
957                 }
958 
959                 var secondaryDexUse = new SecondaryDexUse();
960                 secondaryDexUse.fromProto(secondaryProto,
961                         classLoaderContext
962                         -> validateClassLoaderContext.apply(dexFile, classLoaderContext));
963                 mSecondaryDexUseByDexFile.put(dexFile, secondaryDexUse);
964             }
965         }
966     }
967 
968     private static class PrimaryDexUse {
969         @NonNull Map<DexLoader, PrimaryDexUseRecord> mRecordByLoader = new HashMap<>();
970 
toProto(@onNull PrimaryDexUseProto.Builder builder)971         void toProto(@NonNull PrimaryDexUseProto.Builder builder) {
972             for (var entry : mRecordByLoader.entrySet()) {
973                 var recordBuilder =
974                         PrimaryDexUseRecordProto.newBuilder()
975                                 .setLoadingPackageName(entry.getKey().loadingPackageName())
976                                 .setIsolatedProcess(entry.getKey().isolatedProcess());
977                 entry.getValue().toProto(recordBuilder);
978                 builder.addRecord(recordBuilder);
979             }
980         }
981 
fromProto(@onNull PrimaryDexUseProto proto)982         void fromProto(@NonNull PrimaryDexUseProto proto) {
983             for (PrimaryDexUseRecordProto recordProto : proto.getRecordList()) {
984                 var record = new PrimaryDexUseRecord();
985                 record.fromProto(recordProto);
986                 mRecordByLoader.put(
987                         DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
988                                 recordProto.getIsolatedProcess()),
989                         record);
990             }
991         }
992     }
993 
994     private static class SecondaryDexUse {
995         @Nullable UserHandle mUserHandle = null;
996         @NonNull Map<DexLoader, SecondaryDexUseRecord> mRecordByLoader = new HashMap<>();
997 
toProto(@onNull SecondaryDexUseProto.Builder builder)998         void toProto(@NonNull SecondaryDexUseProto.Builder builder) {
999             builder.setUserId(Int32Value.newBuilder().setValue(mUserHandle.getIdentifier()));
1000             for (var entry : mRecordByLoader.entrySet()) {
1001                 var recordBuilder =
1002                         SecondaryDexUseRecordProto.newBuilder()
1003                                 .setLoadingPackageName(entry.getKey().loadingPackageName())
1004                                 .setIsolatedProcess(entry.getKey().isolatedProcess());
1005                 entry.getValue().toProto(recordBuilder);
1006                 builder.addRecord(recordBuilder);
1007             }
1008         }
1009 
fromProto(@onNull SecondaryDexUseProto proto, @NonNull Function<String, String> validateClassLoaderContext)1010         void fromProto(@NonNull SecondaryDexUseProto proto,
1011                 @NonNull Function<String, String> validateClassLoaderContext) {
1012             Utils.check(proto.hasUserId());
1013             mUserHandle = UserHandle.of(proto.getUserId().getValue());
1014             for (SecondaryDexUseRecordProto recordProto : proto.getRecordList()) {
1015                 // Skip invalid class loader context persisted by previous versions.
1016                 String errorMsg = validateClassLoaderContext.apply(
1017                         Utils.assertNonEmpty(recordProto.getClassLoaderContext()));
1018                 if (errorMsg != null) {
1019                     AsLog.e(errorMsg);
1020                     continue;
1021                 }
1022 
1023                 var record = new SecondaryDexUseRecord();
1024                 record.fromProto(recordProto);
1025 
1026                 if (!Utils.isNativeAbi(record.mAbiName)) {
1027                     // The native ABI set has changed by an OTA since the ABI name was recorded.
1028                     AsLog.i(String.format("Ignoring secondary dex use record with non-native ABI "
1029                                     + "'%s' for '%s'",
1030                             record.mAbiName, proto.getDexFile()));
1031                     continue;
1032                 }
1033 
1034                 mRecordByLoader.put(
1035                         DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
1036                                 recordProto.getIsolatedProcess()),
1037                         record);
1038             }
1039         }
1040     }
1041 
1042     /**
1043      * Represents an entity that loads a dex file.
1044      *
1045      * @hide
1046      */
1047     @Immutable
1048     @AutoValue
1049     public abstract static class DexLoader implements Comparable<DexLoader> {
create(@onNull String loadingPackageName, boolean isolatedProcess)1050         static DexLoader create(@NonNull String loadingPackageName, boolean isolatedProcess) {
1051             return new AutoValue_DexUseManagerLocal_DexLoader(loadingPackageName, isolatedProcess);
1052         }
1053 
loadingPackageName()1054         abstract @NonNull String loadingPackageName();
1055 
1056         /** @see Process#isIsolatedUid(int) */
isolatedProcess()1057         abstract boolean isolatedProcess();
1058 
1059         @Override
1060         @NonNull
toString()1061         public String toString() {
1062             return loadingPackageName() + (isolatedProcess() ? " (isolated)" : "");
1063         }
1064 
1065         @Override
compareTo(DexLoader o)1066         public int compareTo(DexLoader o) {
1067             return Comparator.comparing(DexLoader::loadingPackageName)
1068                     .thenComparing(DexLoader::isolatedProcess)
1069                     .compare(this, o);
1070         }
1071     }
1072 
1073     private static class PrimaryDexUseRecord {
1074         @Nullable long mLastUsedAtMs = 0;
1075 
toProto(@onNull PrimaryDexUseRecordProto.Builder builder)1076         void toProto(@NonNull PrimaryDexUseRecordProto.Builder builder) {
1077             builder.setLastUsedAtMs(mLastUsedAtMs);
1078         }
1079 
fromProto(@onNull PrimaryDexUseRecordProto proto)1080         void fromProto(@NonNull PrimaryDexUseRecordProto proto) {
1081             mLastUsedAtMs = proto.getLastUsedAtMs();
1082             Utils.check(mLastUsedAtMs > 0);
1083         }
1084     }
1085 
1086     private static class SecondaryDexUseRecord {
1087         // An app constructs their own class loader to load a secondary dex file, so only itself
1088         // knows the class loader context. Therefore, we need to record the class loader context
1089         // reported by the app.
1090         @Nullable String mClassLoaderContext = null;
1091         @Nullable String mAbiName = null;
1092         @Nullable long mLastUsedAtMs = 0;
1093 
toProto(@onNull SecondaryDexUseRecordProto.Builder builder)1094         void toProto(@NonNull SecondaryDexUseRecordProto.Builder builder) {
1095             builder.setClassLoaderContext(mClassLoaderContext)
1096                     .setAbiName(mAbiName)
1097                     .setLastUsedAtMs(mLastUsedAtMs);
1098         }
1099 
fromProto(@onNull SecondaryDexUseRecordProto proto)1100         void fromProto(@NonNull SecondaryDexUseRecordProto proto) {
1101             mClassLoaderContext = Utils.assertNonEmpty(proto.getClassLoaderContext());
1102             mAbiName = Utils.assertNonEmpty(proto.getAbiName());
1103             mLastUsedAtMs = proto.getLastUsedAtMs();
1104             Utils.check(mLastUsedAtMs > 0);
1105         }
1106     }
1107 
1108     // TODO(b/278697552): Consider removing the cache or moving it to `Environment`.
1109     static class SecondaryDexLocationManager {
1110         private @NonNull Map<CacheKey, CacheValue> mCache = new HashMap<>();
1111 
getLocations( @onNull PackageState pkgState, @NonNull UserHandle userHandle)1112         public @NonNull List<Path> getLocations(
1113                 @NonNull PackageState pkgState, @NonNull UserHandle userHandle) {
1114             AndroidPackage pkg = pkgState.getAndroidPackage();
1115             if (pkg == null) {
1116                 return List.of();
1117             }
1118 
1119             UUID storageUuid = pkg.getStorageUuid();
1120             String packageName = pkgState.getPackageName();
1121 
1122             CacheKey cacheKey = CacheKey.create(packageName, userHandle);
1123             CacheValue cacheValue = mCache.get(cacheKey);
1124             if (cacheValue != null && cacheValue.storageUuid().equals(storageUuid)) {
1125                 return cacheValue.locations();
1126             }
1127 
1128             File ceDir = Environment.getDataCePackageDirectoryForUser(
1129                     storageUuid, userHandle, packageName);
1130             File deDir = Environment.getDataDePackageDirectoryForUser(
1131                     storageUuid, userHandle, packageName);
1132             List<Path> locations = List.of(ceDir.toPath(), deDir.toPath());
1133             mCache.put(cacheKey, CacheValue.create(locations, storageUuid));
1134             return locations;
1135         }
1136 
1137         @Immutable
1138         @AutoValue
1139         abstract static class CacheKey {
create(@onNull String packageName, @NonNull UserHandle userHandle)1140             static CacheKey create(@NonNull String packageName, @NonNull UserHandle userHandle) {
1141                 return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheKey(
1142                         packageName, userHandle);
1143             }
1144 
packageName()1145             abstract @NonNull String packageName();
1146 
userHandle()1147             abstract @NonNull UserHandle userHandle();
1148         }
1149 
1150         @Immutable
1151         @AutoValue
1152         abstract static class CacheValue {
create(@onNull List<Path> locations, @NonNull UUID storageUuid)1153             static CacheValue create(@NonNull List<Path> locations, @NonNull UUID storageUuid) {
1154                 return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheValue(
1155                         locations, storageUuid);
1156             }
1157 
locations()1158             abstract @NonNull List<Path> locations();
1159 
storageUuid()1160             abstract @NonNull UUID storageUuid();
1161         }
1162     }
1163 
1164     /**
1165      * Injector pattern for testing purpose.
1166      *
1167      * @hide
1168      */
1169     @VisibleForTesting
1170     public static class Injector {
1171         @NonNull private final Context mContext;
1172 
Injector(@onNull Context context)1173         Injector(@NonNull Context context) {
1174             mContext = context;
1175 
1176             // Call the getters for various dependencies, to ensure correct initialization order.
1177             GlobalInjector.getInstance().checkArtModuleServiceManager();
1178             getPackageManagerLocal();
1179         }
1180 
1181         @NonNull
getArtd()1182         public IArtd getArtd() {
1183             return ArtdRefCache.getInstance().getArtd();
1184         }
1185 
getCurrentTimeMillis()1186         public long getCurrentTimeMillis() {
1187             return System.currentTimeMillis();
1188         }
1189 
1190         @NonNull
getFilename()1191         public String getFilename() {
1192             return FILENAME;
1193         }
1194 
1195         @NonNull
createScheduledExecutor()1196         public ScheduledExecutorService createScheduledExecutor() {
1197             return Executors.newScheduledThreadPool(1 /* corePoolSize */);
1198         }
1199 
1200         @NonNull
getContext()1201         public Context getContext() {
1202             return mContext;
1203         }
1204 
1205         @NonNull
getAllPackageNames()1206         public Set<String> getAllPackageNames() {
1207             try (PackageManagerLocal.UnfilteredSnapshot snapshot =
1208                             getPackageManagerLocal().withUnfilteredSnapshot()) {
1209                 return new HashSet<>(snapshot.getPackageStates().keySet());
1210             }
1211         }
1212 
isPreReboot()1213         public boolean isPreReboot() {
1214             return GlobalInjector.getInstance().isPreReboot();
1215         }
1216 
1217         @NonNull
getPackageManagerLocal()1218         private PackageManagerLocal getPackageManagerLocal() {
1219             return Objects.requireNonNull(
1220                     LocalManagerRegistry.getManager(PackageManagerLocal.class));
1221         }
1222 
1223         @NonNull
getArtManagerLocal()1224         public ArtManagerLocal getArtManagerLocal() {
1225             return Objects.requireNonNull(LocalManagerRegistry.getManager(ArtManagerLocal.class));
1226         }
1227     }
1228 }
1229