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