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