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