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