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