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