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