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 17 package com.android.server.pm.dex; 18 19 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; 20 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 21 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; 22 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; 23 24 import static java.util.function.Function.identity; 25 26 import android.content.Context; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.IPackageManager; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackagePartitions; 32 import android.os.BatteryManager; 33 import android.os.FileUtils; 34 import android.os.PowerManager; 35 import android.os.RemoteException; 36 import android.os.SystemProperties; 37 import android.os.UserHandle; 38 import android.os.storage.StorageManager; 39 import android.util.Log; 40 import android.util.Slog; 41 import android.util.jar.StrictJarFile; 42 43 import com.android.internal.annotations.GuardedBy; 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.server.pm.Installer; 46 import com.android.server.pm.Installer.InstallerException; 47 import com.android.server.pm.PackageDexOptimizer; 48 import com.android.server.pm.PackageManagerService; 49 import com.android.server.pm.PackageManagerServiceUtils; 50 51 import dalvik.system.VMRuntime; 52 53 import java.io.File; 54 import java.io.IOException; 55 import java.nio.file.Files; 56 import java.nio.file.Paths; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.Collections; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.Iterator; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Set; 67 import java.util.zip.ZipEntry; 68 69 /** 70 * This class keeps track of how dex files are used. 71 * Every time it gets a notification about a dex file being loaded it tracks 72 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 73 * 74 * TODO(calin): Extract related dexopt functionality from PackageManagerService 75 * into this class. 76 */ 77 public class DexManager { 78 private static final String TAG = "DexManager"; 79 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 80 81 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob"; 82 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = 83 "pm.dexopt.priv-apps-oob-list"; 84 85 // System server cannot load executable code outside system partitions. 86 // However it can load verification data - thus we pick the "verify" compiler filter. 87 private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify"; 88 89 // The suffix we add to the package name when the loading happens in an isolated process. 90 // Note that the double dot creates and "invalid" package name which makes it clear that this 91 // is an artificially constructed name. 92 private static final String ISOLATED_PROCESS_PACKAGE_SUFFIX = "..isolated"; 93 94 private final Context mContext; 95 96 // Maps package name to code locations. 97 // It caches the code locations for the installed packages. This allows for 98 // faster lookups (no locks) when finding what package owns the dex file. 99 @GuardedBy("mPackageCodeLocationsCache") 100 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 101 102 // PackageDexUsage handles the actual I/O operations. It is responsible to 103 // encode and save the dex usage data. 104 private final PackageDexUsage mPackageDexUsage; 105 106 // DynamicCodeLogger handles recording of dynamic code loading - which is similar to 107 // PackageDexUsage but records a different aspect of the data. 108 // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't 109 // record class loaders or ISAs.) 110 private final DynamicCodeLogger mDynamicCodeLogger; 111 112 private final IPackageManager mPackageManager; 113 private final PackageDexOptimizer mPackageDexOptimizer; 114 private final Object mInstallLock; 115 @GuardedBy("mInstallLock") 116 private final Installer mInstaller; 117 118 private BatteryManager mBatteryManager = null; 119 private PowerManager mPowerManager = null; 120 121 // An integer percentage value used to determine when the device is considered to be on low 122 // power for compilation purposes. 123 private final int mCriticalBatteryLevel; 124 125 // Possible outcomes of a dex search. 126 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 127 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 128 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 129 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 130 DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock)131 public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, 132 Installer installer, Object installLock) { 133 mContext = context; 134 mPackageCodeLocationsCache = new HashMap<>(); 135 mPackageDexUsage = new PackageDexUsage(); 136 mPackageManager = pms; 137 mPackageDexOptimizer = pdo; 138 mInstaller = installer; 139 mInstallLock = installLock; 140 mDynamicCodeLogger = new DynamicCodeLogger(pms, installer); 141 142 // This is currently checked to handle tests that pass in a null context. 143 // TODO(b/174783329): Modify the tests to pass in a mocked Context, PowerManager, 144 // and BatteryManager. 145 if (mContext != null) { 146 mPowerManager = mContext.getSystemService(PowerManager.class); 147 148 if (mPowerManager == null) { 149 Slog.wtf(TAG, "Power Manager is unavailable at time of Dex Manager start"); 150 } 151 152 mCriticalBatteryLevel = mContext.getResources().getInteger( 153 com.android.internal.R.integer.config_criticalBatteryWarningLevel); 154 } else { 155 // This value will never be used as the Battery Manager null check will fail first. 156 mCriticalBatteryLevel = 0; 157 } 158 } 159 getDynamicCodeLogger()160 public DynamicCodeLogger getDynamicCodeLogger() { 161 return mDynamicCodeLogger; 162 } 163 164 /** 165 * Notify about dex files loads. 166 * Note that this method is invoked when apps load dex files and it should 167 * return as fast as possible. 168 * 169 * @param loadingAppInfo the package performing the load 170 * @param classLoaderContextMap a map from file paths to dex files that have been loaded to 171 * the class loader context that was used to load them. 172 * @param loaderIsa the ISA of the app loading the dex files 173 * @param loaderUserId the user id which runs the code loading the dex files 174 * @param loaderIsIsolatedProcess whether or not the loading process is isolated. 175 */ notifyDexLoad(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, boolean loaderIsIsolatedProcess)176 public void notifyDexLoad(ApplicationInfo loadingAppInfo, 177 Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, 178 boolean loaderIsIsolatedProcess) { 179 try { 180 notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa, 181 loaderUserId, loaderIsIsolatedProcess); 182 } catch (Exception e) { 183 Slog.w(TAG, "Exception while notifying dex load for package " + 184 loadingAppInfo.packageName, e); 185 } 186 } 187 188 @VisibleForTesting notifyDexLoadInternal(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, boolean loaderIsIsolatedProcess)189 /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, 190 Map<String, String> classLoaderContextMap, String loaderIsa, 191 int loaderUserId, boolean loaderIsIsolatedProcess) { 192 if (classLoaderContextMap == null) { 193 return; 194 } 195 if (classLoaderContextMap.isEmpty()) { 196 Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty"); 197 return; 198 } 199 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 200 Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet() 201 + " in unsupported ISA: " + loaderIsa + "?"); 202 return; 203 } 204 205 // If this load is coming from an isolated process we need to be able to prevent profile 206 // based optimizations. This is because isolated processes are sandboxed and can only read 207 // world readable files, so they need world readable optimization files. An 208 // example of such a package is webview. 209 // 210 // In order to prevent profile optimization we pretend that the load is coming from a 211 // different package, and so we assign a artificial name to the loading package making it 212 // clear that it comes from an isolated process. This blends well with the entire 213 // usedByOthers logic without needing to special handle isolated process in all dexopt 214 // layers. 215 String loadingPackageAmendedName = loadingAppInfo.packageName; 216 if (loaderIsIsolatedProcess) { 217 loadingPackageAmendedName += ISOLATED_PROCESS_PACKAGE_SUFFIX; 218 } 219 for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) { 220 String dexPath = mapping.getKey(); 221 // Find the owning package name. 222 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 223 224 if (DEBUG) { 225 Slog.i(TAG, loadingPackageAmendedName 226 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 227 } 228 229 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 230 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 231 // different apps share the same runtime. In that case we should not mark the dex 232 // file as isUsedByOtherApps. Currently this is a safe approximation. 233 boolean isUsedByOtherApps = 234 !loadingPackageAmendedName.equals(searchResult.mOwningPackageName); 235 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 236 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 237 238 if (primaryOrSplit && !isUsedByOtherApps 239 && !isPlatformPackage(searchResult.mOwningPackageName)) { 240 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 241 // do not record it. This case does not bring any new usable information 242 // and can be safely skipped. 243 // Note this is just an optimization that makes things easier to read in the 244 // package-dex-use file since we don't need to pollute it with redundant info. 245 // However, we always record system server packages. 246 continue; 247 } 248 249 if (!primaryOrSplit) { 250 // Record loading of a DEX file from an app data directory. 251 mDynamicCodeLogger.recordDex(loaderUserId, dexPath, 252 searchResult.mOwningPackageName, loadingAppInfo.packageName); 253 } 254 255 String classLoaderContext = mapping.getValue(); 256 257 // Overwrite the class loader context for system server (instead of merging it). 258 // We expect system server jars to only change contexts in between OTAs and to 259 // otherwise be stable. 260 // Instead of implementing a complex clear-context logic post OTA, it is much 261 // simpler to always override the context for system server. This way, the context 262 // will always be up to date and we will avoid merging which could lead to the 263 // the context being marked as variable and thus making dexopt non-optimal. 264 boolean overwriteCLC = isPlatformPackage(searchResult.mOwningPackageName); 265 266 if (classLoaderContext != null 267 && VMRuntime.isValidClassLoaderContext(classLoaderContext)) { 268 // Record dex file usage. If the current usage is a new pattern (e.g. new 269 // secondary, or UsedByOtherApps), record will return true and we trigger an 270 // async write to disk to make sure we don't loose the data in case of a reboot. 271 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 272 dexPath, loaderUserId, loaderIsa, primaryOrSplit, 273 loadingPackageAmendedName, classLoaderContext, overwriteCLC)) { 274 mPackageDexUsage.maybeWriteAsync(); 275 } 276 } 277 } else { 278 // If we can't find the owner of the dex we simply do not track it. The impact is 279 // that the dex file will not be considered for offline optimizations. 280 if (DEBUG) { 281 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 282 } 283 } 284 } 285 } 286 287 /** 288 * Check if the dexPath belongs to system server. 289 * System server can load code from different location, so we cast a wide-net here, and 290 * assume that if the paths is on any of the registered system partitions then it can be loaded 291 * by system server. 292 */ isSystemServerDexPathSupportedForOdex(String dexPath)293 private boolean isSystemServerDexPathSupportedForOdex(String dexPath) { 294 ArrayList<PackagePartitions.SystemPartition> partitions = 295 PackagePartitions.getOrderedPartitions(identity()); 296 // First check the apex partition as it's not part of the SystemPartitions. 297 if (dexPath.startsWith("/apex/")) { 298 return true; 299 } 300 for (int i = 0; i < partitions.size(); i++) { 301 if (partitions.get(i).containsPath(dexPath)) { 302 return true; 303 } 304 } 305 return false; 306 } 307 308 /** 309 * Read the dex usage from disk and populate the code cache locations. 310 * @param existingPackages a map containing information about what packages 311 * are available to what users. Only packages in this list will be 312 * recognized during notifyDexLoad(). 313 */ load(Map<Integer, List<PackageInfo>> existingPackages)314 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 315 try { 316 loadInternal(existingPackages); 317 } catch (Exception e) { 318 mPackageDexUsage.clear(); 319 mDynamicCodeLogger.clear(); 320 Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); 321 } 322 } 323 324 /** 325 * Notifies that a new package was installed for {@code userId}. 326 * {@code userId} must not be {@code UserHandle.USER_ALL}. 327 * 328 * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}. 329 */ notifyPackageInstalled(PackageInfo pi, int userId)330 public void notifyPackageInstalled(PackageInfo pi, int userId) { 331 if (userId == UserHandle.USER_ALL) { 332 throw new IllegalArgumentException( 333 "notifyPackageInstalled called with USER_ALL"); 334 } 335 cachePackageInfo(pi, userId); 336 } 337 338 /** 339 * Notifies that package {@code packageName} was updated. 340 * This will clear the UsedByOtherApps mark if it exists. 341 */ notifyPackageUpdated(String packageName, String baseCodePath, String[] splitCodePaths)342 public void notifyPackageUpdated(String packageName, String baseCodePath, 343 String[] splitCodePaths) { 344 cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1); 345 // In case there was an update, write the package use info to disk async. 346 // Note that we do the writing here and not in PackageDexUsage in order to be 347 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 348 // multiple updates in PackageDexUsage before writing it). 349 if (mPackageDexUsage.clearUsedByOtherApps(packageName)) { 350 mPackageDexUsage.maybeWriteAsync(); 351 } 352 } 353 354 /** 355 * Notifies that the user {@code userId} data for package {@code packageName} 356 * was destroyed. This will remove all usage info associated with the package 357 * for the given user. 358 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case 359 * all usage information for the package will be removed. 360 */ notifyPackageDataDestroyed(String packageName, int userId)361 public void notifyPackageDataDestroyed(String packageName, int userId) { 362 // In case there was an update, write the package use info to disk async. 363 // Note that we do the writing here and not in the lower level classes in order to be 364 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 365 // multiple updates in PackageDexUsage before writing it). 366 if (userId == UserHandle.USER_ALL) { 367 if (mPackageDexUsage.removePackage(packageName)) { 368 mPackageDexUsage.maybeWriteAsync(); 369 } 370 mDynamicCodeLogger.removePackage(packageName); 371 } else { 372 if (mPackageDexUsage.removeUserPackage(packageName, userId)) { 373 mPackageDexUsage.maybeWriteAsync(); 374 } 375 mDynamicCodeLogger.removeUserPackage(packageName, userId); 376 } 377 } 378 379 /** 380 * Caches the code location from the given package info. 381 */ cachePackageInfo(PackageInfo pi, int userId)382 private void cachePackageInfo(PackageInfo pi, int userId) { 383 ApplicationInfo ai = pi.applicationInfo; 384 String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir, 385 ai.credentialProtectedDataDir}; 386 cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs, 387 dataDirs, userId); 388 } 389 cachePackageCodeLocation(String packageName, String baseCodePath, String[] splitCodePaths, String[] dataDirs, int userId)390 private void cachePackageCodeLocation(String packageName, String baseCodePath, 391 String[] splitCodePaths, String[] dataDirs, int userId) { 392 synchronized (mPackageCodeLocationsCache) { 393 PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, 394 new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); 395 // TODO(calin): We are forced to extend the scope of this synchronization because 396 // the values of the cache (PackageCodeLocations) are updated in place. 397 // Make PackageCodeLocations immutable to simplify the synchronization reasoning. 398 pcl.updateCodeLocation(baseCodePath, splitCodePaths); 399 if (dataDirs != null) { 400 for (String dataDir : dataDirs) { 401 // The set of data dirs includes deviceProtectedDataDir and 402 // credentialProtectedDataDir which might be null for shared 403 // libraries. Currently we don't track these but be lenient 404 // and check in case we ever decide to store their usage data. 405 if (dataDir != null) { 406 pcl.mergeAppDataDirs(dataDir, userId); 407 } 408 } 409 } 410 } 411 } 412 loadInternal(Map<Integer, List<PackageInfo>> existingPackages)413 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 414 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 415 Map<String, Set<String>> packageToCodePaths = new HashMap<>(); 416 417 // Cache the code locations for the installed packages. This allows for 418 // faster lookups (no locks) when finding what package owns the dex file. 419 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 420 List<PackageInfo> packageInfoList = entry.getValue(); 421 int userId = entry.getKey(); 422 for (PackageInfo pi : packageInfoList) { 423 // Cache the code locations. 424 cachePackageInfo(pi, userId); 425 426 // Cache two maps: 427 // - from package name to the set of user ids who installed the package. 428 // - from package name to the set of code paths. 429 // We will use it to sync the data and remove obsolete entries from 430 // mPackageDexUsage. 431 Set<Integer> users = putIfAbsent( 432 packageToUsersMap, pi.packageName, new HashSet<>()); 433 users.add(userId); 434 435 Set<String> codePaths = putIfAbsent( 436 packageToCodePaths, pi.packageName, new HashSet<>()); 437 codePaths.add(pi.applicationInfo.sourceDir); 438 if (pi.applicationInfo.splitSourceDirs != null) { 439 Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs); 440 } 441 } 442 } 443 444 try { 445 mPackageDexUsage.read(); 446 List<String> packagesToKeepDataAbout = new ArrayList<>(); 447 mPackageDexUsage.syncData( 448 packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout); 449 } catch (Exception e) { 450 mPackageDexUsage.clear(); 451 Slog.w(TAG, "Exception while loading package dex usage. " 452 + "Starting with a fresh state.", e); 453 } 454 455 try { 456 mDynamicCodeLogger.readAndSync(packageToUsersMap); 457 } catch (Exception e) { 458 mDynamicCodeLogger.clear(); 459 Slog.w(TAG, "Exception while loading package dynamic code usage. " 460 + "Starting with a fresh state.", e); 461 } 462 } 463 464 /** 465 * Get the package dex usage for the given package name. 466 * If there is no usage info the method will return a default {@code PackageUseInfo} with 467 * no data about secondary dex files and marked as not being used by other apps. 468 * 469 * Note that no use info means the package was not used or it was used but not by other apps. 470 * Also, note that right now we might prune packages which are not used by other apps. 471 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 472 * to access the package use. 473 */ getPackageUseInfoOrDefault(String packageName)474 public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { 475 // We do not record packages that have no secondary dex files or that are not used by other 476 // apps. This is an optimization to reduce the amount of data that needs to be written to 477 // disk (apps will not usually be shared so this trims quite a bit the number we record). 478 // 479 // To make this behaviour transparent to the callers which need use information on packages, 480 // DexManager will return this DEFAULT instance from 481 // {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files 482 // and is marked as not being used by other apps. This reflects the intended behaviour when 483 // we don't find the package in the underlying data file. 484 PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); 485 return useInfo == null ? new PackageUseInfo(packageName) : useInfo; 486 } 487 488 /** 489 * Return whether or not the manager has usage information on the give package. 490 * 491 * Note that no use info means the package was not used or it was used but not by other apps. 492 * Also, note that right now we might prune packages which are not used by other apps. 493 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 494 * to access the package use. 495 */ 496 @VisibleForTesting hasInfoOnPackage(String packageName)497 /*package*/ boolean hasInfoOnPackage(String packageName) { 498 return mPackageDexUsage.getPackageUseInfo(packageName) != null; 499 } 500 501 /** 502 * Perform dexopt on with the given {@code options} on the secondary dex files. 503 * @return true if all secondary dex files were processed successfully (compiled or skipped 504 * because they don't need to be compiled).. 505 */ dexoptSecondaryDex(DexoptOptions options)506 public boolean dexoptSecondaryDex(DexoptOptions options) { 507 if (isPlatformPackage(options.getPackageName())) { 508 // We could easily redirect to #dexoptSystemServer in this case. But there should be 509 // no-one calling this method directly for system server. 510 // As such we prefer to abort in this case. 511 Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer"); 512 return false; 513 } 514 515 PackageDexOptimizer pdo = getPackageDexOptimizer(options); 516 String packageName = options.getPackageName(); 517 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 518 if (useInfo.getDexUseInfoMap().isEmpty()) { 519 if (DEBUG) { 520 Slog.d(TAG, "No secondary dex use for package:" + packageName); 521 } 522 // Nothing to compile, return true. 523 return true; 524 } 525 boolean success = true; 526 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 527 String dexPath = entry.getKey(); 528 DexUseInfo dexUseInfo = entry.getValue(); 529 530 PackageInfo pkg; 531 try { 532 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 533 dexUseInfo.getOwnerUserId()); 534 } catch (RemoteException e) { 535 throw new AssertionError(e); 536 } 537 // It may be that the package gets uninstalled while we try to compile its 538 // secondary dex files. If that's the case, just ignore. 539 // Note that we don't break the entire loop because the package might still be 540 // installed for other users. 541 if (pkg == null) { 542 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 543 + " for user " + dexUseInfo.getOwnerUserId()); 544 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); 545 continue; 546 } 547 548 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, 549 dexUseInfo, options); 550 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); 551 } 552 return success; 553 } 554 555 /** 556 * Performs dexopt on system server dex files. 557 * 558 * <p>Verfifies that the package name is {@link PackageManagerService#PLATFORM_PACKAGE_NAME}. 559 * 560 * @return 561 * <p>PackageDexOptimizer.DEX_OPT_SKIPPED if dexopt was skipped because no system server 562 * files were recorded or if no dexopt was needed. 563 * <p>PackageDexOptimizer.DEX_OPT_FAILED if any dexopt operation failed. 564 * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded. 565 */ dexoptSystemServer(DexoptOptions options)566 public int dexoptSystemServer(DexoptOptions options) { 567 if (!isPlatformPackage(options.getPackageName())) { 568 Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:" 569 + options.getPackageName()); 570 return PackageDexOptimizer.DEX_OPT_FAILED; 571 } 572 573 // Override compiler filter for system server to the expected one. 574 // 575 // We could let the caller do this every time the invoke PackageManagerServer#dexopt. 576 // However, there are a few places were this will need to be done which creates 577 // redundancy and the danger of overlooking the config (and thus generating code that will 578 // waste storage and time). 579 DexoptOptions overriddenOptions = options.overrideCompilerFilter( 580 SYSTEM_SERVER_COMPILER_FILTER); 581 582 PackageDexOptimizer pdo = getPackageDexOptimizer(overriddenOptions); 583 String packageName = overriddenOptions.getPackageName(); 584 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 585 if (useInfo.getDexUseInfoMap().isEmpty()) { 586 if (DEBUG) { 587 Slog.d(TAG, "No dex files recorded for system server"); 588 } 589 // Nothing to compile, return true. 590 return PackageDexOptimizer.DEX_OPT_SKIPPED; 591 } 592 593 boolean usageUpdated = false; 594 int result = PackageDexOptimizer.DEX_OPT_SKIPPED; 595 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 596 String dexPath = entry.getKey(); 597 DexUseInfo dexUseInfo = entry.getValue(); 598 if (!Files.exists(Paths.get(dexPath))) { 599 if (DEBUG) { 600 Slog.w(TAG, "A dex file previously loaded by System Server does not exist " 601 + " anymore: " + dexPath); 602 } 603 usageUpdated = mPackageDexUsage.removeDexFile( 604 packageName, dexPath, dexUseInfo.getOwnerUserId()) || usageUpdated; 605 continue; 606 } 607 608 if (dexUseInfo.isUnsupportedClassLoaderContext() 609 || dexUseInfo.isVariableClassLoaderContext()) { 610 String debugMsg = dexUseInfo.isUnsupportedClassLoaderContext() 611 ? "unsupported" 612 : "variable"; 613 Slog.w(TAG, "Skipping dexopt for system server path loaded with " + debugMsg 614 + " class loader context: " + dexPath); 615 continue; 616 } 617 618 int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, overriddenOptions); 619 620 // The end result is: 621 // - FAILED if any path failed, 622 // - PERFORMED if at least one path needed compilation, 623 // - SKIPPED when all paths are up to date 624 if ((result != PackageDexOptimizer.DEX_OPT_FAILED) 625 && (newResult != PackageDexOptimizer.DEX_OPT_SKIPPED)) { 626 result = newResult; 627 } 628 } 629 630 if (usageUpdated) { 631 mPackageDexUsage.maybeWriteAsync(); 632 } 633 634 return result; 635 } 636 637 /** 638 * Select the dex optimizer based on the force parameter. 639 * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust 640 * the necessary dexopt flags to make sure that compilation is not skipped. This avoid 641 * passing the force flag through the multitude of layers. 642 * Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to 643 * allocate an object here. 644 */ getPackageDexOptimizer(DexoptOptions options)645 private PackageDexOptimizer getPackageDexOptimizer(DexoptOptions options) { 646 return options.isForce() 647 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) 648 : mPackageDexOptimizer; 649 } 650 651 /** 652 * Reconcile the information we have about the secondary dex files belonging to 653 * {@code packagName} and the actual dex files. For all dex files that were 654 * deleted, update the internal records and delete any generated oat files. 655 */ reconcileSecondaryDexFiles(String packageName)656 public void reconcileSecondaryDexFiles(String packageName) { 657 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 658 if (useInfo.getDexUseInfoMap().isEmpty()) { 659 if (DEBUG) { 660 Slog.d(TAG, "No secondary dex use for package:" + packageName); 661 } 662 // Nothing to reconcile. 663 return; 664 } 665 666 boolean updated = false; 667 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 668 String dexPath = entry.getKey(); 669 DexUseInfo dexUseInfo = entry.getValue(); 670 PackageInfo pkg = null; 671 try { 672 // Note that we look for the package in the PackageManager just to be able 673 // to get back the real app uid and its storage kind. These are only used 674 // to perform extra validation in installd. 675 // TODO(calin): maybe a bit overkill. 676 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 677 dexUseInfo.getOwnerUserId()); 678 } catch (RemoteException ignore) { 679 // Can't happen, DexManager is local. 680 } 681 if (pkg == null) { 682 // It may be that the package was uninstalled while we process the secondary 683 // dex files. 684 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 685 + " for user " + dexUseInfo.getOwnerUserId()); 686 // Update the usage and continue, another user might still have the package. 687 updated = mPackageDexUsage.removeUserPackage( 688 packageName, dexUseInfo.getOwnerUserId()) || updated; 689 continue; 690 } 691 692 // Special handle system server files. 693 // We don't need an installd call because we have permissions to check if the file 694 // exists. 695 if (isPlatformPackage(packageName)) { 696 if (!Files.exists(Paths.get(dexPath))) { 697 if (DEBUG) { 698 Slog.w(TAG, "A dex file previously loaded by System Server does not exist " 699 + " anymore: " + dexPath); 700 } 701 updated = mPackageDexUsage.removeUserPackage( 702 packageName, dexUseInfo.getOwnerUserId()) || updated; 703 } 704 continue; 705 } 706 707 // This is a regular application. 708 ApplicationInfo info = pkg.applicationInfo; 709 int flags = 0; 710 if (info.deviceProtectedDataDir != null && 711 FileUtils.contains(info.deviceProtectedDataDir, dexPath)) { 712 flags |= StorageManager.FLAG_STORAGE_DE; 713 } else if (info.credentialProtectedDataDir!= null && 714 FileUtils.contains(info.credentialProtectedDataDir, dexPath)) { 715 flags |= StorageManager.FLAG_STORAGE_CE; 716 } else { 717 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath); 718 updated = mPackageDexUsage.removeDexFile( 719 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 720 continue; 721 } 722 723 boolean dexStillExists = true; 724 synchronized(mInstallLock) { 725 try { 726 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); 727 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, 728 info.uid, isas, info.volumeUuid, flags); 729 } catch (InstallerException e) { 730 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + 731 " : " + e.getMessage()); 732 } 733 } 734 if (!dexStillExists) { 735 updated = mPackageDexUsage.removeDexFile( 736 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 737 } 738 739 } 740 if (updated) { 741 mPackageDexUsage.maybeWriteAsync(); 742 } 743 } 744 745 // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the 746 // compilation happening here will use a pessimistic context. registerDexModule(ApplicationInfo info, String dexPath, boolean isSharedModule, int userId)747 public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, 748 boolean isSharedModule, int userId) { 749 // Find the owning package record. 750 DexSearchResult searchResult = getDexPackage(info, dexPath, userId); 751 752 if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) { 753 return new RegisterDexModuleResult(false, "Package not found"); 754 } 755 if (!info.packageName.equals(searchResult.mOwningPackageName)) { 756 return new RegisterDexModuleResult(false, "Dex path does not belong to package"); 757 } 758 if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 759 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) { 760 return new RegisterDexModuleResult(false, "Main apks cannot be registered"); 761 } 762 763 // We found the package. Now record the usage for all declared ISAs. 764 boolean update = false; 765 // If this is a shared module set the loading package to an arbitrary package name 766 // so that we can mark that module as usedByOthers. 767 String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName; 768 for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) { 769 boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, 770 dexPath, userId, isa, /*primaryOrSplit*/ false, 771 loadingPackage, 772 PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, 773 /*overwriteCLC=*/ false); 774 update |= newUpdate; 775 } 776 if (update) { 777 mPackageDexUsage.maybeWriteAsync(); 778 } 779 780 DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName) 781 .getDexUseInfoMap().get(dexPath); 782 783 // Try to optimize the package according to the install reason. 784 DexoptOptions options = new DexoptOptions(info.packageName, 785 PackageManagerService.REASON_INSTALL, /*flags*/0); 786 787 int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo, 788 options); 789 790 // If we fail to optimize the package log an error but don't propagate the error 791 // back to the app. The app cannot do much about it and the background job 792 // will rety again when it executes. 793 // TODO(calin): there might be some value to return the error here but it may 794 // cause red herrings since that doesn't mean the app cannot use the module. 795 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 796 Slog.e(TAG, "Failed to optimize dex module " + dexPath); 797 } 798 return new RegisterDexModuleResult(true, "Dex module registered successfully"); 799 } 800 801 /** 802 * Return all packages that contain records of secondary dex files. 803 */ getAllPackagesWithSecondaryDexFiles()804 public Set<String> getAllPackagesWithSecondaryDexFiles() { 805 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); 806 } 807 808 /** 809 * Retrieves the package which owns the given dexPath. 810 */ getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId)811 private DexSearchResult getDexPackage( 812 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 813 // First, check if the package which loads the dex file actually owns it. 814 // Most of the time this will be true and we can return early. 815 PackageCodeLocations loadingPackageCodeLocations = 816 new PackageCodeLocations(loadingAppInfo, userId); 817 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 818 if (outcome != DEX_SEARCH_NOT_FOUND) { 819 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 820 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 821 } 822 823 // The loadingPackage does not own the dex file. 824 // Perform a reverse look-up in the cache to detect if any package has ownership. 825 // Note that we can have false negatives if the cache falls out of date. 826 synchronized (mPackageCodeLocationsCache) { 827 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 828 outcome = pcl.searchDex(dexPath, userId); 829 if (outcome != DEX_SEARCH_NOT_FOUND) { 830 return new DexSearchResult(pcl.mPackageName, outcome); 831 } 832 } 833 } 834 835 // We could not find the owning package amongst regular apps. 836 // If the loading package is system server, see if the dex file resides 837 // on any of the potentially system server owning location and if so, 838 // assuming system server ownership. 839 // 840 // Note: We don't have any way to detect which code paths are actually 841 // owned by system server. We can only assume that such paths are on 842 // system partitions. 843 if (isPlatformPackage(loadingAppInfo.packageName)) { 844 if (isSystemServerDexPathSupportedForOdex(dexPath)) { 845 // We record system server dex files as secondary dex files. 846 // The reason is that we only record the class loader context for secondary dex 847 // files and we expect that all primary apks are loaded with an empty class loader. 848 // System server dex files may be loaded in non-empty class loader so we need to 849 // keep track of their context. 850 return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY); 851 } else { 852 Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: " 853 + dexPath); 854 } 855 } 856 857 if (DEBUG) { 858 // TODO(calin): Consider checking for /data/data symlink. 859 // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps 860 // to load dex files through it. 861 try { 862 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 863 if (!dexPath.equals(dexPathReal)) { 864 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 865 dexPath + " dexPathReal=" + dexPathReal); 866 } 867 } catch (IOException e) { 868 // Ignore 869 } 870 } 871 // Cache miss. The cache is updated during installs and uninstalls, 872 // so if we get here we're pretty sure the dex path does not exist. 873 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 874 } 875 876 /** Returns true if this is the platform package .*/ isPlatformPackage(String packageName)877 private static boolean isPlatformPackage(String packageName) { 878 return PLATFORM_PACKAGE_NAME.equals(packageName); 879 } 880 putIfAbsent(Map<K,V> map, K key, V newValue)881 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 882 V existingValue = map.putIfAbsent(key, newValue); 883 return existingValue == null ? newValue : existingValue; 884 } 885 886 /** 887 * Writes the in-memory package dex usage to disk right away. 888 */ writePackageDexUsageNow()889 public void writePackageDexUsageNow() { 890 mPackageDexUsage.writeNow(); 891 mDynamicCodeLogger.writeNow(); 892 } 893 894 /** 895 * Returns whether the given package is in the list of privilaged apps that should run out of 896 * box. This only makes sense if the feature is enabled. Note that when the the OOB list is 897 * empty, all priv apps will run in OOB mode. 898 */ isPackageSelectedToRunOob(String packageName)899 public static boolean isPackageSelectedToRunOob(String packageName) { 900 return isPackageSelectedToRunOob(Arrays.asList(packageName)); 901 } 902 903 /** 904 * Returns whether any of the given packages are in the list of privilaged apps that should run 905 * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list 906 * is empty, all priv apps will run in OOB mode. 907 */ isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess)908 public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) { 909 return isPackageSelectedToRunOobInternal( 910 SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false), 911 SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"), 912 packageNamesInSameProcess); 913 } 914 915 @VisibleForTesting isPackageSelectedToRunOobInternal(boolean isEnabled, String whitelist, Collection<String> packageNamesInSameProcess)916 /* package */ static boolean isPackageSelectedToRunOobInternal(boolean isEnabled, 917 String whitelist, Collection<String> packageNamesInSameProcess) { 918 if (!isEnabled) { 919 return false; 920 } 921 922 if ("ALL".equals(whitelist)) { 923 return true; 924 } 925 for (String oobPkgName : whitelist.split(",")) { 926 if (packageNamesInSameProcess.contains(oobPkgName)) { 927 return true; 928 } 929 } 930 return false; 931 } 932 933 /** 934 * Generates log if the archive located at {@code fileName} has uncompressed dex file that can 935 * be direclty mapped. 936 */ auditUncompressedDexInApk(String fileName)937 public static boolean auditUncompressedDexInApk(String fileName) { 938 StrictJarFile jarFile = null; 939 try { 940 jarFile = new StrictJarFile(fileName, 941 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/); 942 Iterator<ZipEntry> it = jarFile.iterator(); 943 boolean allCorrect = true; 944 while (it.hasNext()) { 945 ZipEntry entry = it.next(); 946 if (entry.getName().endsWith(".dex")) { 947 if (entry.getMethod() != ZipEntry.STORED) { 948 allCorrect = false; 949 Slog.w(TAG, "APK " + fileName + " has compressed dex code " + 950 entry.getName()); 951 } else if ((entry.getDataOffset() & 0x3) != 0) { 952 allCorrect = false; 953 Slog.w(TAG, "APK " + fileName + " has unaligned dex code " + 954 entry.getName()); 955 } 956 } 957 } 958 return allCorrect; 959 } catch (IOException ignore) { 960 Slog.wtf(TAG, "Error when parsing APK " + fileName); 961 return false; 962 } finally { 963 try { 964 if (jarFile != null) { 965 jarFile.close(); 966 } 967 } catch (IOException ignore) {} 968 } 969 } 970 971 /** 972 * Translates install scenarios into compilation reasons. This process can be influenced 973 * by the state of the device. 974 */ getCompilationReasonForInstallScenario(int installScenario)975 public int getCompilationReasonForInstallScenario(int installScenario) { 976 // Compute the compilation reason from the installation scenario. 977 978 boolean resourcesAreCritical = areBatteryThermalOrMemoryCritical(); 979 switch (installScenario) { 980 case PackageManager.INSTALL_SCENARIO_DEFAULT: { 981 return PackageManagerService.REASON_INSTALL; 982 } 983 case PackageManager.INSTALL_SCENARIO_FAST: { 984 return PackageManagerService.REASON_INSTALL_FAST; 985 } 986 case PackageManager.INSTALL_SCENARIO_BULK: { 987 if (resourcesAreCritical) { 988 return PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED; 989 } else { 990 return PackageManagerService.REASON_INSTALL_BULK; 991 } 992 } 993 case PackageManager.INSTALL_SCENARIO_BULK_SECONDARY: { 994 if (resourcesAreCritical) { 995 return PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED; 996 } else { 997 return PackageManagerService.REASON_INSTALL_BULK_SECONDARY; 998 } 999 } 1000 default: { 1001 throw new IllegalArgumentException("Invalid installation scenario"); 1002 } 1003 } 1004 } 1005 1006 /** 1007 * Fetches the battery manager object and caches it if it hasn't been fetched already. 1008 */ getBatteryManager()1009 private BatteryManager getBatteryManager() { 1010 if (mBatteryManager == null && mContext != null) { 1011 mBatteryManager = mContext.getSystemService(BatteryManager.class); 1012 } 1013 1014 return mBatteryManager; 1015 } 1016 1017 /** 1018 * Returns true if the battery level, device temperature, or memory usage are considered to be 1019 * in a critical state. 1020 */ areBatteryThermalOrMemoryCritical()1021 private boolean areBatteryThermalOrMemoryCritical() { 1022 BatteryManager batteryManager = getBatteryManager(); 1023 boolean isBtmCritical = (batteryManager != null 1024 && batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) 1025 == BatteryManager.BATTERY_STATUS_DISCHARGING 1026 && batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) 1027 <= mCriticalBatteryLevel) 1028 || (mPowerManager != null 1029 && mPowerManager.getCurrentThermalStatus() 1030 >= PowerManager.THERMAL_STATUS_SEVERE); 1031 1032 return isBtmCritical; 1033 } 1034 1035 /** 1036 * Deletes all the optimizations files generated by ART. 1037 * This is best effort, and the method will log but not throw errors 1038 * for individual deletes 1039 * 1040 * @param packageInfo the package information. 1041 * @return the number of freed bytes or -1 if there was an error in the process. 1042 */ deleteOptimizedFiles(ArtPackageInfo packageInfo)1043 public long deleteOptimizedFiles(ArtPackageInfo packageInfo) { 1044 long freedBytes = 0; 1045 boolean hadErrors = false; 1046 for (String codePath : packageInfo.getCodePaths()) { 1047 for (String isa : packageInfo.getInstructionSets()) { 1048 try { 1049 freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir()); 1050 } catch (InstallerException e) { 1051 Log.e(TAG, "Failed deleting oat files for " + codePath, e); 1052 hadErrors = true; 1053 } 1054 } 1055 } 1056 return hadErrors ? -1 : freedBytes; 1057 } 1058 1059 public static class RegisterDexModuleResult { RegisterDexModuleResult()1060 public RegisterDexModuleResult() { 1061 this(false, null); 1062 } 1063 RegisterDexModuleResult(boolean success, String message)1064 public RegisterDexModuleResult(boolean success, String message) { 1065 this.success = success; 1066 this.message = message; 1067 } 1068 1069 public final boolean success; 1070 public final String message; 1071 } 1072 1073 /** 1074 * Convenience class to store the different locations where a package might 1075 * own code. 1076 */ 1077 private static class PackageCodeLocations { 1078 private final String mPackageName; 1079 private String mBaseCodePath; 1080 private final Set<String> mSplitCodePaths; 1081 // Maps user id to the application private directory. 1082 private final Map<Integer, Set<String>> mAppDataDirs; 1083 PackageCodeLocations(ApplicationInfo ai, int userId)1084 public PackageCodeLocations(ApplicationInfo ai, int userId) { 1085 this(ai.packageName, ai.sourceDir, ai.splitSourceDirs); 1086 mergeAppDataDirs(ai.dataDir, userId); 1087 } PackageCodeLocations(String packageName, String baseCodePath, String[] splitCodePaths)1088 public PackageCodeLocations(String packageName, String baseCodePath, 1089 String[] splitCodePaths) { 1090 mPackageName = packageName; 1091 mSplitCodePaths = new HashSet<>(); 1092 mAppDataDirs = new HashMap<>(); 1093 updateCodeLocation(baseCodePath, splitCodePaths); 1094 } 1095 updateCodeLocation(String baseCodePath, String[] splitCodePaths)1096 public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) { 1097 mBaseCodePath = baseCodePath; 1098 mSplitCodePaths.clear(); 1099 if (splitCodePaths != null) { 1100 for (String split : splitCodePaths) { 1101 mSplitCodePaths.add(split); 1102 } 1103 } 1104 } 1105 mergeAppDataDirs(String dataDir, int userId)1106 public void mergeAppDataDirs(String dataDir, int userId) { 1107 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 1108 dataDirs.add(dataDir); 1109 } 1110 searchDex(String dexPath, int userId)1111 public int searchDex(String dexPath, int userId) { 1112 // First check that this package is installed or active for the given user. 1113 // A missing data dir means the package is not installed. 1114 Set<String> userDataDirs = mAppDataDirs.get(userId); 1115 if (userDataDirs == null) { 1116 return DEX_SEARCH_NOT_FOUND; 1117 } 1118 1119 if (mBaseCodePath.equals(dexPath)) { 1120 return DEX_SEARCH_FOUND_PRIMARY; 1121 } 1122 if (mSplitCodePaths.contains(dexPath)) { 1123 return DEX_SEARCH_FOUND_SPLIT; 1124 } 1125 for (String dataDir : userDataDirs) { 1126 if (dexPath.startsWith(dataDir)) { 1127 return DEX_SEARCH_FOUND_SECONDARY; 1128 } 1129 } 1130 1131 return DEX_SEARCH_NOT_FOUND; 1132 } 1133 } 1134 1135 /** 1136 * Convenience class to store ownership search results. 1137 */ 1138 private class DexSearchResult { 1139 private String mOwningPackageName; 1140 private int mOutcome; 1141 DexSearchResult(String owningPackageName, int outcome)1142 public DexSearchResult(String owningPackageName, int outcome) { 1143 this.mOwningPackageName = owningPackageName; 1144 this.mOutcome = outcome; 1145 } 1146 1147 @Override toString()1148 public String toString() { 1149 return mOwningPackageName + "-" + mOutcome; 1150 } 1151 } 1152 } 1153