DexManager.java revision 535a4753e313bdc2ae3e8be9f50606b82edcce0c
1/* 2 * Copyright (C) 2016 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 17package com.android.server.pm.dex; 18 19import android.content.pm.ApplicationInfo; 20import android.content.pm.IPackageManager; 21import android.content.pm.PackageInfo; 22import android.os.FileUtils; 23import android.os.RemoteException; 24import android.os.storage.StorageManager; 25import android.os.UserHandle; 26 27import android.util.Slog; 28 29import com.android.internal.annotations.GuardedBy; 30import com.android.server.pm.Installer; 31import com.android.server.pm.Installer.InstallerException; 32import com.android.server.pm.PackageDexOptimizer; 33import com.android.server.pm.PackageManagerService; 34import com.android.server.pm.PackageManagerServiceUtils; 35import com.android.server.pm.PackageManagerServiceCompilerMapping; 36 37import java.io.File; 38import java.io.IOException; 39import java.util.List; 40import java.util.HashMap; 41import java.util.HashSet; 42import java.util.Map; 43import java.util.Set; 44 45import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; 46import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; 47import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; 48 49/** 50 * This class keeps track of how dex files are used. 51 * Every time it gets a notification about a dex file being loaded it tracks 52 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 53 * 54 * TODO(calin): Extract related dexopt functionality from PackageManagerService 55 * into this class. 56 */ 57public class DexManager { 58 private static final String TAG = "DexManager"; 59 60 private static final boolean DEBUG = false; 61 62 // Maps package name to code locations. 63 // It caches the code locations for the installed packages. This allows for 64 // faster lookups (no locks) when finding what package owns the dex file. 65 @GuardedBy("mPackageCodeLocationsCache") 66 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 67 68 // PackageDexUsage handles the actual I/O operations. It is responsible to 69 // encode and save the dex usage data. 70 private final PackageDexUsage mPackageDexUsage; 71 72 private final IPackageManager mPackageManager; 73 private final PackageDexOptimizer mPackageDexOptimizer; 74 private final Object mInstallLock; 75 @GuardedBy("mInstallLock") 76 private final Installer mInstaller; 77 78 // Possible outcomes of a dex search. 79 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 80 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 81 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 82 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 83 84 public DexManager(IPackageManager pms, PackageDexOptimizer pdo, 85 Installer installer, Object installLock) { 86 mPackageCodeLocationsCache = new HashMap<>(); 87 mPackageDexUsage = new PackageDexUsage(); 88 mPackageManager = pms; 89 mPackageDexOptimizer = pdo; 90 mInstaller = installer; 91 mInstallLock = installLock; 92 } 93 94 /** 95 * Notify about dex files loads. 96 * Note that this method is invoked when apps load dex files and it should 97 * return as fast as possible. 98 * 99 * @param loadingAppInfo the package performing the load 100 * @param dexPaths the list of dex files being loaded 101 * @param loaderIsa the ISA of the app loading the dex files 102 * @param loaderUserId the user id which runs the code loading the dex files 103 */ 104 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, 105 String loaderIsa, int loaderUserId) { 106 try { 107 notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); 108 } catch (Exception e) { 109 Slog.w(TAG, "Exception while notifying dex load for package " + 110 loadingAppInfo.packageName, e); 111 } 112 } 113 114 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, 115 String loaderIsa, int loaderUserId) { 116 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 117 Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + 118 loaderIsa + "?"); 119 return; 120 } 121 122 for (String dexPath : dexPaths) { 123 // Find the owning package name. 124 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 125 126 if (DEBUG) { 127 Slog.i(TAG, loadingAppInfo.packageName 128 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 129 } 130 131 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 132 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 133 // different apps share the same runtime. In that case we should not mark the dex 134 // file as isUsedByOtherApps. Currently this is a safe approximation. 135 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 136 searchResult.mOwningPackageName); 137 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 138 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 139 140 if (primaryOrSplit && !isUsedByOtherApps) { 141 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 142 // do not record it. This case does not bring any new usable information 143 // and can be safely skipped. 144 continue; 145 } 146 147 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, 148 // or UsedBytOtherApps), record will return true and we trigger an async write 149 // to disk to make sure we don't loose the data in case of a reboot. 150 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 151 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, 152 loadingAppInfo.packageName)) { 153 mPackageDexUsage.maybeWriteAsync(); 154 } 155 } else { 156 // This can happen in a few situations: 157 // - bogus dex loads 158 // - recent installs/uninstalls that we didn't detect. 159 // - new installed splits 160 // If we can't find the owner of the dex we simply do not track it. The impact is 161 // that the dex file will not be considered for offline optimizations. 162 // TODO(calin): add hooks for move/uninstall notifications to 163 // capture package moves or obsolete packages. 164 if (DEBUG) { 165 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 166 } 167 } 168 } 169 } 170 171 /** 172 * Read the dex usage from disk and populate the code cache locations. 173 * @param existingPackages a map containing information about what packages 174 * are available to what users. Only packages in this list will be 175 * recognized during notifyDexLoad(). 176 */ 177 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 178 try { 179 loadInternal(existingPackages); 180 } catch (Exception e) { 181 mPackageDexUsage.clear(); 182 Slog.w(TAG, "Exception while loading package dex usage. " + 183 "Starting with a fresh state.", e); 184 } 185 } 186 187 /** 188 * Notifies that a new package was installed for {@code userId}. 189 * {@code userId} must not be {@code UserHandle.USER_ALL}. 190 * 191 * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}. 192 */ 193 public void notifyPackageInstalled(PackageInfo pi, int userId) { 194 if (userId == UserHandle.USER_ALL) { 195 throw new IllegalArgumentException( 196 "notifyPackageInstalled called with USER_ALL"); 197 } 198 cachePackageInfo(pi, userId); 199 } 200 201 /** 202 * Notifies that package {@code packageName} was updated. 203 * This will clear the UsedByOtherApps mark if it exists. 204 */ 205 public void notifyPackageUpdated(String packageName, String baseCodePath, 206 String[] splitCodePaths) { 207 cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1); 208 // In case there was an update, write the package use info to disk async. 209 // Note that we do the writing here and not in PackageDexUsage in order to be 210 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 211 // multiple updates in PackageDexUsage before writing it). 212 if (mPackageDexUsage.clearUsedByOtherApps(packageName)) { 213 mPackageDexUsage.maybeWriteAsync(); 214 } 215 } 216 217 /** 218 * Notifies that the user {@code userId} data for package {@code packageName} 219 * was destroyed. This will remove all usage info associated with the package 220 * for the given user. 221 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case 222 * all usage information for the package will be removed. 223 */ 224 public void notifyPackageDataDestroyed(String packageName, int userId) { 225 boolean updated = userId == UserHandle.USER_ALL 226 ? mPackageDexUsage.removePackage(packageName) 227 : mPackageDexUsage.removeUserPackage(packageName, userId); 228 // In case there was an update, write the package use info to disk async. 229 // Note that we do the writing here and not in PackageDexUsage in order to be 230 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 231 // multiple updates in PackageDexUsage before writing it). 232 if (updated) { 233 mPackageDexUsage.maybeWriteAsync(); 234 } 235 } 236 237 /** 238 * Caches the code location from the given package info. 239 */ 240 private void cachePackageInfo(PackageInfo pi, int userId) { 241 ApplicationInfo ai = pi.applicationInfo; 242 String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir, 243 ai.credentialProtectedDataDir}; 244 cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs, 245 dataDirs, userId); 246 } 247 248 private void cachePackageCodeLocation(String packageName, String baseCodePath, 249 String[] splitCodePaths, String[] dataDirs, int userId) { 250 synchronized (mPackageCodeLocationsCache) { 251 PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, 252 new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); 253 // TODO(calin): We are forced to extend the scope of this synchronization because 254 // the values of the cache (PackageCodeLocations) are updated in place. 255 // Make PackageCodeLocations immutable to simplify the synchronization reasoning. 256 pcl.updateCodeLocation(baseCodePath, splitCodePaths); 257 if (dataDirs != null) { 258 for (String dataDir : dataDirs) { 259 // The set of data dirs includes deviceProtectedDataDir and 260 // credentialProtectedDataDir which might be null for shared 261 // libraries. Currently we don't track these but be lenient 262 // and check in case we ever decide to store their usage data. 263 if (dataDir != null) { 264 pcl.mergeAppDataDirs(dataDir, userId); 265 } 266 } 267 } 268 } 269 } 270 271 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 272 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 273 // Cache the code locations for the installed packages. This allows for 274 // faster lookups (no locks) when finding what package owns the dex file. 275 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 276 List<PackageInfo> packageInfoList = entry.getValue(); 277 int userId = entry.getKey(); 278 for (PackageInfo pi : packageInfoList) { 279 // Cache the code locations. 280 cachePackageInfo(pi, userId); 281 282 // Cache a map from package name to the set of user ids who installed the package. 283 // We will use it to sync the data and remove obsolete entries from 284 // mPackageDexUsage. 285 Set<Integer> users = putIfAbsent( 286 packageToUsersMap, pi.packageName, new HashSet<>()); 287 users.add(userId); 288 } 289 } 290 291 mPackageDexUsage.read(); 292 mPackageDexUsage.syncData(packageToUsersMap); 293 } 294 295 /** 296 * Get the package dex usage for the given package name. 297 * @return the package data or null if there is no data available for this package. 298 */ 299 public PackageUseInfo getPackageUseInfo(String packageName) { 300 return mPackageDexUsage.getPackageUseInfo(packageName); 301 } 302 303 /** 304 * Perform dexopt on the package {@code packageName} secondary dex files. 305 * @return true if all secondary dex files were processed successfully (compiled or skipped 306 * because they don't need to be compiled).. 307 */ 308 public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) { 309 return dexoptSecondaryDex(packageName, 310 PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason), 311 force, /* compileOnlySharedDex */ false); 312 } 313 314 /** 315 * Perform dexopt on the package {@code packageName} secondary dex files. 316 * @return true if all secondary dex files were processed successfully (compiled or skipped 317 * because they don't need to be compiled).. 318 */ 319 public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force, 320 boolean compileOnlySharedDex) { 321 // Select the dex optimizer based on the force parameter. 322 // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust 323 // the necessary dexopt flags to make sure that compilation is not skipped. This avoid 324 // passing the force flag through the multitude of layers. 325 // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to 326 // allocate an object here. 327 PackageDexOptimizer pdo = force 328 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) 329 : mPackageDexOptimizer; 330 PackageUseInfo useInfo = getPackageUseInfo(packageName); 331 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 332 if (DEBUG) { 333 Slog.d(TAG, "No secondary dex use for package:" + packageName); 334 } 335 // Nothing to compile, return true. 336 return true; 337 } 338 boolean success = true; 339 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 340 String dexPath = entry.getKey(); 341 DexUseInfo dexUseInfo = entry.getValue(); 342 if (compileOnlySharedDex && !dexUseInfo.isUsedByOtherApps()) { 343 continue; 344 } 345 PackageInfo pkg = null; 346 try { 347 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 348 dexUseInfo.getOwnerUserId()); 349 } catch (RemoteException e) { 350 throw new AssertionError(e); 351 } 352 // It may be that the package gets uninstalled while we try to compile its 353 // secondary dex files. If that's the case, just ignore. 354 // Note that we don't break the entire loop because the package might still be 355 // installed for other users. 356 if (pkg == null) { 357 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 358 + " for user " + dexUseInfo.getOwnerUserId()); 359 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); 360 continue; 361 } 362 363 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, 364 dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); 365 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); 366 } 367 return success; 368 } 369 370 /** 371 * Reconcile the information we have about the secondary dex files belonging to 372 * {@code packagName} and the actual dex files. For all dex files that were 373 * deleted, update the internal records and delete any generated oat files. 374 */ 375 public void reconcileSecondaryDexFiles(String packageName) { 376 PackageUseInfo useInfo = getPackageUseInfo(packageName); 377 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 378 if (DEBUG) { 379 Slog.d(TAG, "No secondary dex use for package:" + packageName); 380 } 381 // Nothing to reconcile. 382 return; 383 } 384 385 boolean updated = false; 386 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 387 String dexPath = entry.getKey(); 388 DexUseInfo dexUseInfo = entry.getValue(); 389 PackageInfo pkg = null; 390 try { 391 // Note that we look for the package in the PackageManager just to be able 392 // to get back the real app uid and its storage kind. These are only used 393 // to perform extra validation in installd. 394 // TODO(calin): maybe a bit overkill. 395 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 396 dexUseInfo.getOwnerUserId()); 397 } catch (RemoteException ignore) { 398 // Can't happen, DexManager is local. 399 } 400 if (pkg == null) { 401 // It may be that the package was uninstalled while we process the secondary 402 // dex files. 403 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 404 + " for user " + dexUseInfo.getOwnerUserId()); 405 // Update the usage and continue, another user might still have the package. 406 updated = mPackageDexUsage.removeUserPackage( 407 packageName, dexUseInfo.getOwnerUserId()) || updated; 408 continue; 409 } 410 ApplicationInfo info = pkg.applicationInfo; 411 int flags = 0; 412 if (info.deviceProtectedDataDir != null && 413 FileUtils.contains(info.deviceProtectedDataDir, dexPath)) { 414 flags |= StorageManager.FLAG_STORAGE_DE; 415 } else if (info.credentialProtectedDataDir!= null && 416 FileUtils.contains(info.credentialProtectedDataDir, dexPath)) { 417 flags |= StorageManager.FLAG_STORAGE_CE; 418 } else { 419 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath); 420 updated = mPackageDexUsage.removeDexFile( 421 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 422 continue; 423 } 424 425 boolean dexStillExists = true; 426 synchronized(mInstallLock) { 427 try { 428 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); 429 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, 430 pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags); 431 } catch (InstallerException e) { 432 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + 433 " : " + e.getMessage()); 434 } 435 } 436 if (!dexStillExists) { 437 updated = mPackageDexUsage.removeDexFile( 438 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 439 } 440 441 } 442 if (updated) { 443 mPackageDexUsage.maybeWriteAsync(); 444 } 445 } 446 447 public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, 448 boolean isUsedByOtherApps, int userId) { 449 // Find the owning package record. 450 DexSearchResult searchResult = getDexPackage(info, dexPath, userId); 451 452 if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) { 453 return new RegisterDexModuleResult(false, "Package not found"); 454 } 455 if (!info.packageName.equals(searchResult.mOwningPackageName)) { 456 return new RegisterDexModuleResult(false, "Dex path does not belong to package"); 457 } 458 if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 459 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) { 460 return new RegisterDexModuleResult(false, "Main apks cannot be registered"); 461 } 462 463 // We found the package. Now record the usage for all declared ISAs. 464 boolean update = false; 465 Set<String> isas = new HashSet<>(); 466 for (String isa : getAppDexInstructionSets(info)) { 467 isas.add(isa); 468 boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, 469 dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, 470 searchResult.mOwningPackageName); 471 update |= newUpdate; 472 } 473 if (update) { 474 mPackageDexUsage.maybeWriteAsync(); 475 } 476 477 // Try to optimize the package according to the install reason. 478 String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason( 479 PackageManagerService.REASON_INSTALL); 480 int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas, 481 compilerFilter, isUsedByOtherApps); 482 483 // If we fail to optimize the package log an error but don't propagate the error 484 // back to the app. The app cannot do much about it and the background job 485 // will rety again when it executes. 486 // TODO(calin): there might be some value to return the error here but it may 487 // cause red herrings since that doesn't mean the app cannot use the module. 488 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 489 Slog.e(TAG, "Failed to optimize dex module " + dexPath); 490 } 491 return new RegisterDexModuleResult(true, "Dex module registered successfully"); 492 } 493 494 /** 495 * Return all packages that contain records of secondary dex files. 496 */ 497 public Set<String> getAllPackagesWithSecondaryDexFiles() { 498 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); 499 } 500 501 /** 502 * Return true if the profiling data collected for the given app indicate 503 * that the apps's APK has been loaded by another app. 504 * Note that this returns false for all apps without any collected profiling data. 505 */ 506 public boolean isUsedByOtherApps(String packageName) { 507 PackageUseInfo useInfo = getPackageUseInfo(packageName); 508 if (useInfo == null) { 509 // No use info, means the package was not used or it was used but not by other apps. 510 // Note that right now we might prune packages which are not used by other apps. 511 // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 512 // to access the package use. 513 return false; 514 } 515 return useInfo.isUsedByOtherApps(); 516 } 517 518 /** 519 * Retrieves the package which owns the given dexPath. 520 */ 521 private DexSearchResult getDexPackage( 522 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 523 // Ignore framework code. 524 // TODO(calin): is there a better way to detect it? 525 if (dexPath.startsWith("/system/framework/")) { 526 return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); 527 } 528 529 // First, check if the package which loads the dex file actually owns it. 530 // Most of the time this will be true and we can return early. 531 PackageCodeLocations loadingPackageCodeLocations = 532 new PackageCodeLocations(loadingAppInfo, userId); 533 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 534 if (outcome != DEX_SEARCH_NOT_FOUND) { 535 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 536 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 537 } 538 539 // The loadingPackage does not own the dex file. 540 // Perform a reverse look-up in the cache to detect if any package has ownership. 541 // Note that we can have false negatives if the cache falls out of date. 542 synchronized (mPackageCodeLocationsCache) { 543 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 544 outcome = pcl.searchDex(dexPath, userId); 545 if (outcome != DEX_SEARCH_NOT_FOUND) { 546 return new DexSearchResult(pcl.mPackageName, outcome); 547 } 548 } 549 } 550 551 if (DEBUG) { 552 // TODO(calin): Consider checking for /data/data symlink. 553 // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps 554 // to load dex files through it. 555 try { 556 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 557 if (dexPathReal != dexPath) { 558 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 559 dexPath + " dexPathReal=" + dexPathReal); 560 } 561 } catch (IOException e) { 562 // Ignore 563 } 564 } 565 // Cache miss. The cache is updated during installs and uninstalls, 566 // so if we get here we're pretty sure the dex path does not exist. 567 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 568 } 569 570 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 571 V existingValue = map.putIfAbsent(key, newValue); 572 return existingValue == null ? newValue : existingValue; 573 } 574 575 public static class RegisterDexModuleResult { 576 public RegisterDexModuleResult() { 577 this(false, null); 578 } 579 580 public RegisterDexModuleResult(boolean success, String message) { 581 this.success = success; 582 this.message = message; 583 } 584 585 public final boolean success; 586 public final String message; 587 } 588 589 /** 590 * Convenience class to store the different locations where a package might 591 * own code. 592 */ 593 private static class PackageCodeLocations { 594 private final String mPackageName; 595 private String mBaseCodePath; 596 private final Set<String> mSplitCodePaths; 597 // Maps user id to the application private directory. 598 private final Map<Integer, Set<String>> mAppDataDirs; 599 600 public PackageCodeLocations(ApplicationInfo ai, int userId) { 601 this(ai.packageName, ai.sourceDir, ai.splitSourceDirs); 602 mergeAppDataDirs(ai.dataDir, userId); 603 } 604 public PackageCodeLocations(String packageName, String baseCodePath, 605 String[] splitCodePaths) { 606 mPackageName = packageName; 607 mSplitCodePaths = new HashSet<>(); 608 mAppDataDirs = new HashMap<>(); 609 updateCodeLocation(baseCodePath, splitCodePaths); 610 } 611 612 public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) { 613 mBaseCodePath = baseCodePath; 614 mSplitCodePaths.clear(); 615 if (splitCodePaths != null) { 616 for (String split : splitCodePaths) { 617 mSplitCodePaths.add(split); 618 } 619 } 620 } 621 622 public void mergeAppDataDirs(String dataDir, int userId) { 623 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 624 dataDirs.add(dataDir); 625 } 626 627 public int searchDex(String dexPath, int userId) { 628 // First check that this package is installed or active for the given user. 629 // A missing data dir means the package is not installed. 630 Set<String> userDataDirs = mAppDataDirs.get(userId); 631 if (userDataDirs == null) { 632 return DEX_SEARCH_NOT_FOUND; 633 } 634 635 if (mBaseCodePath.equals(dexPath)) { 636 return DEX_SEARCH_FOUND_PRIMARY; 637 } 638 if (mSplitCodePaths.contains(dexPath)) { 639 return DEX_SEARCH_FOUND_SPLIT; 640 } 641 for (String dataDir : userDataDirs) { 642 if (dexPath.startsWith(dataDir)) { 643 return DEX_SEARCH_FOUND_SECONDARY; 644 } 645 } 646 647 return DEX_SEARCH_NOT_FOUND; 648 } 649 } 650 651 /** 652 * Convenience class to store ownership search results. 653 */ 654 private class DexSearchResult { 655 private String mOwningPackageName; 656 private int mOutcome; 657 658 public DexSearchResult(String owningPackageName, int outcome) { 659 this.mOwningPackageName = owningPackageName; 660 this.mOutcome = outcome; 661 } 662 663 @Override 664 public String toString() { 665 return mOwningPackageName + "-" + mOutcome; 666 } 667 } 668} 669