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 android.os.Build; 20 import android.util.AtomicFile; 21 import android.util.Slog; 22 23 import com.android.internal.annotations.GuardedBy; 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.util.FastPrintWriter; 26 import com.android.server.pm.AbstractStatsBase; 27 import com.android.server.pm.PackageManagerServiceUtils; 28 29 import dalvik.system.VMRuntime; 30 31 import libcore.io.IoUtils; 32 33 import java.io.BufferedReader; 34 import java.io.FileNotFoundException; 35 import java.io.FileOutputStream; 36 import java.io.IOException; 37 import java.io.InputStreamReader; 38 import java.io.OutputStreamWriter; 39 import java.io.Reader; 40 import java.io.StringWriter; 41 import java.io.Writer; 42 import java.util.Collections; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.Iterator; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Set; 49 50 /** 51 * Stat file which store usage information about dex files. 52 */ 53 public class PackageDexUsage extends AbstractStatsBase<Void> { 54 private final static String TAG = "PackageDexUsage"; 55 56 // We support previous version to ensure that the usage list remains valid cross OTAs. 57 private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1; 58 // Version 2 added: 59 // - the list of packages that load the dex files 60 // - class loader contexts for secondary dex files 61 // - usage for all code paths (including splits) 62 private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2 = 2; 63 64 private final static int PACKAGE_DEX_USAGE_VERSION = PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; 65 66 private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = 67 "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; 68 69 private final static String SPLIT_CHAR = ","; 70 private final static String CODE_PATH_LINE_CHAR = "+"; 71 private final static String DEX_LINE_CHAR = "#"; 72 private final static String LOADING_PACKAGE_CHAR = "@"; 73 74 // One of the things we record about dex files is the class loader context that was used to 75 // load them. That should be stable but if it changes we don't keep track of variable contexts. 76 // Instead we put a special marker in the dex usage file in order to recognize the case and 77 // skip optimizations on that dex files. 78 /*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT = 79 "=VariableClassLoaderContext="; 80 // The markers used for unknown class loader contexts. This can happen if the dex file was 81 // recorded in a previous version and we didn't have a chance to update its usage. 82 /*package*/ static final String UNKNOWN_CLASS_LOADER_CONTEXT = 83 "=UnknownClassLoaderContext="; 84 85 // The marker used for unsupported class loader contexts (no longer written, may occur in old 86 // files so discarded on read). Note: this matches 87 // ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime. 88 /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = 89 "=UnsupportedClassLoaderContext="; 90 91 /** 92 * Limit on how many secondary DEX paths we store for a single owner, to avoid one app causing 93 * unbounded memory consumption. 94 */ 95 @VisibleForTesting 96 /* package */ static final int MAX_SECONDARY_FILES_PER_OWNER = 100; 97 98 // Map which structures the information we have on a package. 99 // Maps package name to package data (which stores info about UsedByOtherApps and 100 // secondary dex files.). 101 // Access to this map needs synchronized. 102 @GuardedBy("mPackageUseInfoMap") 103 private final Map<String, PackageUseInfo> mPackageUseInfoMap; 104 PackageDexUsage()105 /* package */ PackageDexUsage() { 106 super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false); 107 mPackageUseInfoMap = new HashMap<>(); 108 } 109 110 /** 111 * Record a dex file load. 112 * 113 * Note this is called when apps load dex files and as such it should return 114 * as fast as possible. 115 * 116 * @param owningPackageName the package owning the dex path 117 * @param dexPath the path of the dex files being loaded 118 * @param ownerUserId the user id which runs the code loading the dex files 119 * @param loaderIsa the ISA of the app loading the dex files 120 * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package 121 * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates 122 * the file is either primary or a split. False indicates the file is secondary dex. 123 * @param loadingPackageName the package performing the load. Recorded only if it is different 124 * than {@param owningPackageName}. 125 * @return true if the dex load constitutes new information, or false if this information 126 * has been seen before. 127 */ record(String owningPackageName, String dexPath, int ownerUserId, String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String loadingPackageName, String classLoaderContext)128 /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId, 129 String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, 130 String loadingPackageName, String classLoaderContext) { 131 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 132 throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); 133 } 134 if (classLoaderContext == null) { 135 throw new IllegalArgumentException("Null classLoaderContext"); 136 } 137 if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) { 138 return false; 139 } 140 141 synchronized (mPackageUseInfoMap) { 142 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); 143 if (packageUseInfo == null) { 144 // This is the first time we see the package. 145 packageUseInfo = new PackageUseInfo(); 146 if (primaryOrSplit) { 147 // If we have a primary or a split apk, set isUsedByOtherApps. 148 // We do not need to record the loaderIsa or the owner because we compile 149 // primaries for all users and all ISAs. 150 packageUseInfo.mergeCodePathUsedByOtherApps(dexPath, isUsedByOtherApps, 151 owningPackageName, loadingPackageName); 152 } else { 153 // For secondary dex files record the loaderISA and the owner. We'll need 154 // to know under which user to compile and for what ISA. 155 DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, 156 classLoaderContext, loaderIsa); 157 packageUseInfo.mDexUseInfoMap.put(dexPath, newData); 158 maybeAddLoadingPackage(owningPackageName, loadingPackageName, 159 newData.mLoadingPackages); 160 } 161 mPackageUseInfoMap.put(owningPackageName, packageUseInfo); 162 return true; 163 } else { 164 // We already have data on this package. Amend it. 165 if (primaryOrSplit) { 166 // We have a possible update on the primary apk usage. Merge 167 // isUsedByOtherApps information and return if there was an update. 168 return packageUseInfo.mergeCodePathUsedByOtherApps( 169 dexPath, isUsedByOtherApps, owningPackageName, loadingPackageName); 170 } else { 171 DexUseInfo newData = new DexUseInfo( 172 isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); 173 boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, 174 loadingPackageName, newData.mLoadingPackages); 175 176 DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); 177 if (existingData == null) { 178 // It's the first time we see this dex file. 179 if (packageUseInfo.mDexUseInfoMap.size() < MAX_SECONDARY_FILES_PER_OWNER) { 180 packageUseInfo.mDexUseInfoMap.put(dexPath, newData); 181 return true; 182 } else { 183 return updateLoadingPackages; 184 } 185 } else { 186 if (ownerUserId != existingData.mOwnerUserId) { 187 // Oups, this should never happen, the DexManager who calls this should 188 // do the proper checks and not call record if the user does not own the 189 // dex path. 190 // Secondary dex files are stored in the app user directory. A change in 191 // owningUser for the same path means that something went wrong at some 192 // higher level, and the loaderUser was allowed to cross 193 // user-boundaries and access data from what we know to be the owner 194 // user. 195 throw new IllegalArgumentException("Trying to change ownerUserId for " 196 + " dex path " + dexPath + " from " + existingData.mOwnerUserId 197 + " to " + ownerUserId); 198 } 199 // Merge the information into the existing data. 200 // Returns true if there was an update. 201 return existingData.merge(newData) || updateLoadingPackages; 202 } 203 } 204 } 205 } 206 } 207 208 /** 209 * Convenience method for sync reads which does not force the user to pass a useless 210 * (Void) null. 211 */ read()212 /* package */ void read() { 213 read((Void) null); 214 } 215 216 /** 217 * Convenience method for async writes which does not force the user to pass a useless 218 * (Void) null. 219 */ maybeWriteAsync()220 /*package*/ void maybeWriteAsync() { 221 maybeWriteAsync(null); 222 } 223 writeNow()224 /*package*/ void writeNow() { 225 writeInternal(null); 226 } 227 228 @Override writeInternal(Void data)229 protected void writeInternal(Void data) { 230 AtomicFile file = getFile(); 231 FileOutputStream f = null; 232 233 try { 234 f = file.startWrite(); 235 OutputStreamWriter osw = new OutputStreamWriter(f); 236 write(osw); 237 osw.flush(); 238 file.finishWrite(f); 239 } catch (IOException e) { 240 if (f != null) { 241 file.failWrite(f); 242 } 243 Slog.e(TAG, "Failed to write usage for dex files", e); 244 } 245 } 246 247 /** 248 * File format: 249 * 250 * file_magic_version 251 * package_name_1 252 * +code_path1 253 * @ loading_package_1_1, loading_package_1_2... 254 * +code_path2 255 * @ loading_package_2_1, loading_package_2_2... 256 * #dex_file_path_1_1 257 * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 258 * @ loading_package_1_1_1, loading_package_1_1_2... 259 * class_loader_context_1_1 260 * #dex_file_path_1_2 261 * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 262 * @ loading_package_1_2_1, loading_package_1_2_2... 263 * class_loader_context_1_2 264 * ... 265 */ write(Writer out)266 /* package */ void write(Writer out) { 267 // Make a clone to avoid locking while writing to disk. 268 Map<String, PackageUseInfo> packageUseInfoMapClone = clonePackageUseInfoMap(); 269 270 FastPrintWriter fpw = new FastPrintWriter(out); 271 272 // Write the header. 273 fpw.print(PACKAGE_DEX_USAGE_VERSION_HEADER); 274 fpw.println(PACKAGE_DEX_USAGE_VERSION); 275 276 for (Map.Entry<String, PackageUseInfo> pEntry : packageUseInfoMapClone.entrySet()) { 277 // Write the package line. 278 String packageName = pEntry.getKey(); 279 PackageUseInfo packageUseInfo = pEntry.getValue(); 280 fpw.println(packageName); 281 282 // Write the code paths used by other apps. 283 for (Map.Entry<String, Set<String>> codeEntry : 284 packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { 285 String codePath = codeEntry.getKey(); 286 Set<String> loadingPackages = codeEntry.getValue(); 287 fpw.println(CODE_PATH_LINE_CHAR + codePath); 288 fpw.println(LOADING_PACKAGE_CHAR + String.join(SPLIT_CHAR, loadingPackages)); 289 } 290 291 // Write dex file lines. 292 for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { 293 String dexPath = dEntry.getKey(); 294 DexUseInfo dexUseInfo = dEntry.getValue(); 295 fpw.println(DEX_LINE_CHAR + dexPath); 296 fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), 297 writeBoolean(dexUseInfo.mIsUsedByOtherApps))); 298 for (String isa : dexUseInfo.mLoaderIsas) { 299 fpw.print(SPLIT_CHAR + isa); 300 } 301 fpw.println(); 302 fpw.println(LOADING_PACKAGE_CHAR 303 + String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages)); 304 fpw.println(dexUseInfo.getClassLoaderContext()); 305 } 306 } 307 fpw.flush(); 308 } 309 310 @Override readInternal(Void data)311 protected void readInternal(Void data) { 312 AtomicFile file = getFile(); 313 BufferedReader in = null; 314 try { 315 in = new BufferedReader(new InputStreamReader(file.openRead())); 316 read(in); 317 } catch (FileNotFoundException expected) { 318 // The file may not be there. E.g. When we first take the OTA with this feature. 319 } catch (IOException e) { 320 Slog.w(TAG, "Failed to parse package dex usage.", e); 321 } finally { 322 IoUtils.closeQuietly(in); 323 } 324 } 325 read(Reader reader)326 /* package */ void read(Reader reader) throws IOException { 327 Map<String, PackageUseInfo> data = new HashMap<>(); 328 BufferedReader in = new BufferedReader(reader); 329 // Read header, do version check. 330 String versionLine = in.readLine(); 331 int version; 332 if (versionLine == null) { 333 throw new IllegalStateException("No version line found."); 334 } else { 335 if (!versionLine.startsWith(PACKAGE_DEX_USAGE_VERSION_HEADER)) { 336 // TODO(calin): the caller is responsible to clear the file. 337 throw new IllegalStateException("Invalid version line: " + versionLine); 338 } 339 version = Integer.parseInt( 340 versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); 341 if (!isSupportedVersion(version)) { 342 throw new IllegalStateException("Unexpected version: " + version); 343 } 344 } 345 346 String line; 347 String currentPackage = null; 348 PackageUseInfo currentPackageData = null; 349 350 Set<String> supportedIsas = new HashSet<>(); 351 for (String abi : Build.SUPPORTED_ABIS) { 352 supportedIsas.add(VMRuntime.getInstructionSet(abi)); 353 } 354 while ((line = in.readLine()) != null) { 355 if (line.startsWith(DEX_LINE_CHAR)) { 356 // This is the start of the the dex lines. 357 // We expect 4 lines for each dex entry: 358 // #dexPaths 359 // @loading_package_1,loading_package_2,... 360 // class_loader_context 361 // onwerUserId,isUsedByOtherApps,isa1,isa2 362 if (currentPackage == null) { 363 throw new IllegalStateException( 364 "Malformed PackageDexUsage file. Expected package line before dex line."); 365 } 366 367 // Line 1 is the dex path. 368 String dexPath = line.substring(DEX_LINE_CHAR.length()); 369 370 // Line 2 is the dex data: (userId, isUsedByOtherApps, isa). 371 line = in.readLine(); 372 if (line == null) { 373 throw new IllegalStateException("Could not find dexUseInfo line"); 374 } 375 String[] elems = line.split(SPLIT_CHAR); 376 if (elems.length < 3) { 377 throw new IllegalStateException("Invalid PackageDexUsage line: " + line); 378 } 379 380 // In version 2 we added the loading packages and class loader context. 381 Set<String> loadingPackages = maybeReadLoadingPackages(in, version); 382 String classLoaderContext = maybeReadClassLoaderContext(in, version); 383 384 if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(classLoaderContext)) { 385 // We used to record use of unsupported class loaders, but we no longer do. 386 // Discard such entries; they will be deleted when we next write the file. 387 continue; 388 } 389 390 int ownerUserId = Integer.parseInt(elems[0]); 391 boolean isUsedByOtherApps = readBoolean(elems[1]); 392 DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId, 393 classLoaderContext, /*isa*/ null); 394 dexUseInfo.mLoadingPackages.addAll(loadingPackages); 395 for (int i = 2; i < elems.length; i++) { 396 String isa = elems[i]; 397 if (supportedIsas.contains(isa)) { 398 dexUseInfo.mLoaderIsas.add(elems[i]); 399 } else { 400 // Should never happen unless someone crafts the file manually. 401 // In theory it could if we drop a supported ISA after an OTA but we don't 402 // do that. 403 Slog.wtf(TAG, "Unsupported ISA when parsing PackageDexUsage: " + isa); 404 } 405 } 406 if (supportedIsas.isEmpty()) { 407 Slog.wtf(TAG, "Ignore dexPath when parsing PackageDexUsage because of " + 408 "unsupported isas. dexPath=" + dexPath); 409 continue; 410 } 411 currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); 412 } else if (line.startsWith(CODE_PATH_LINE_CHAR)) { 413 // This is a code path used by other apps line. 414 if (version < PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { 415 throw new IllegalArgumentException("Unexpected code path line when parsing " + 416 "PackageDexUseData: " + line); 417 } 418 419 // Expects 2 lines: 420 // +code_paths 421 // @loading_packages 422 String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); 423 Set<String> loadingPackages = maybeReadLoadingPackages(in, version); 424 currentPackageData.mCodePathsUsedByOtherApps.put(codePath, loadingPackages); 425 } else { 426 // This is a package line. 427 if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { 428 currentPackage = line; 429 currentPackageData = new PackageUseInfo(); 430 } else { 431 // Old version (<2) 432 // We expect it to be: `packageName,isUsedByOtherApps`. 433 String[] elems = line.split(SPLIT_CHAR); 434 if (elems.length != 2) { 435 throw new IllegalStateException("Invalid PackageDexUsage line: " + line); 436 } 437 currentPackage = elems[0]; 438 currentPackageData = new PackageUseInfo(); 439 currentPackageData.mUsedByOtherAppsBeforeUpgrade = readBoolean(elems[1]); 440 } 441 data.put(currentPackage, currentPackageData); 442 } 443 } 444 445 synchronized (mPackageUseInfoMap) { 446 mPackageUseInfoMap.clear(); 447 mPackageUseInfoMap.putAll(data); 448 } 449 } 450 451 /** 452 * Reads the class loader context encoding from the buffer {@code in} if 453 * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}. 454 */ maybeReadClassLoaderContext(BufferedReader in, int version)455 private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { 456 String context = null; 457 if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { 458 context = in.readLine(); 459 if (context == null) { 460 throw new IllegalStateException("Could not find the classLoaderContext line."); 461 } 462 } 463 // The context might be empty if we didn't have the chance to update it after a version 464 // upgrade. In this case return the special marker so that we recognize this is an unknown 465 // context. 466 return context == null ? UNKNOWN_CLASS_LOADER_CONTEXT : context; 467 } 468 469 /** 470 * Reads the list of loading packages from the buffer {@code in} if 471 * {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}. 472 */ maybeReadLoadingPackages(BufferedReader in, int version)473 private Set<String> maybeReadLoadingPackages(BufferedReader in, int version) 474 throws IOException { 475 if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { 476 String line = in.readLine(); 477 if (line == null) { 478 throw new IllegalStateException("Could not find the loadingPackages line."); 479 } 480 // We expect that most of the times the list of loading packages will be empty. 481 if (line.length() == LOADING_PACKAGE_CHAR.length()) { 482 return Collections.emptySet(); 483 } else { 484 Set<String> result = new HashSet<>(); 485 Collections.addAll(result, 486 line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); 487 return result; 488 } 489 } else { 490 return Collections.emptySet(); 491 } 492 } 493 494 /** 495 * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's 496 * not equal to {@param owningPackage} 497 */ maybeAddLoadingPackage(String owningPackage, String loadingPackage, Set<String> loadingPackages)498 private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage, 499 Set<String> loadingPackages) { 500 return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage); 501 } 502 isSupportedVersion(int version)503 private boolean isSupportedVersion(int version) { 504 return version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 505 || version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; 506 } 507 508 /** 509 * Syncs the existing data with the set of available packages by removing obsolete entries. 510 */ syncData(Map<String, Set<Integer>> packageToUsersMap, Map<String, Set<String>> packageToCodePaths)511 /*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap, 512 Map<String, Set<String>> packageToCodePaths) { 513 synchronized (mPackageUseInfoMap) { 514 Iterator<Map.Entry<String, PackageUseInfo>> pIt = 515 mPackageUseInfoMap.entrySet().iterator(); 516 while (pIt.hasNext()) { 517 Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); 518 String packageName = pEntry.getKey(); 519 PackageUseInfo packageUseInfo = pEntry.getValue(); 520 Set<Integer> users = packageToUsersMap.get(packageName); 521 if (users == null) { 522 // The package doesn't exist anymore, remove the record. 523 pIt.remove(); 524 } else { 525 // The package exists but we can prune the entries associated with non existing 526 // users. 527 Iterator<Map.Entry<String, DexUseInfo>> dIt = 528 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 529 while (dIt.hasNext()) { 530 DexUseInfo dexUseInfo = dIt.next().getValue(); 531 if (!users.contains(dexUseInfo.mOwnerUserId)) { 532 // User was probably removed. Delete its dex usage info. 533 dIt.remove(); 534 } 535 } 536 537 // Sync the code paths. 538 Set<String> codePaths = packageToCodePaths.get(packageName); 539 Iterator<Map.Entry<String, Set<String>>> codeIt = 540 packageUseInfo.mCodePathsUsedByOtherApps.entrySet().iterator(); 541 while (codeIt.hasNext()) { 542 if (!codePaths.contains(codeIt.next().getKey())) { 543 codeIt.remove(); 544 } 545 } 546 547 // In case the package was marked as used by other apps in a previous version 548 // propagate the flag to all the code paths. 549 // See mUsedByOtherAppsBeforeUpgrade docs on why it is important to do it. 550 if (packageUseInfo.mUsedByOtherAppsBeforeUpgrade) { 551 for (String codePath : codePaths) { 552 packageUseInfo.mergeCodePathUsedByOtherApps(codePath, true, null, null); 553 } 554 } else if (!packageUseInfo.isAnyCodePathUsedByOtherApps() 555 && packageUseInfo.mDexUseInfoMap.isEmpty()) { 556 // The package is not used by other apps and we removed all its dex files 557 // records. Remove the entire package record as well. 558 pIt.remove(); 559 } 560 } 561 } 562 } 563 } 564 565 /** 566 * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. 567 * @return true if the package usage info was updated. 568 */ clearUsedByOtherApps(String packageName)569 /*package*/ boolean clearUsedByOtherApps(String packageName) { 570 synchronized (mPackageUseInfoMap) { 571 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 572 if (packageUseInfo == null) { 573 return false; 574 } 575 return packageUseInfo.clearCodePathUsedByOtherApps(); 576 } 577 } 578 579 /** 580 * Remove the usage data associated with package {@code packageName}. 581 * @return true if the package usage was found and removed successfully. 582 */ removePackage(String packageName)583 /* package */ boolean removePackage(String packageName) { 584 synchronized (mPackageUseInfoMap) { 585 return mPackageUseInfoMap.remove(packageName) != null; 586 } 587 } 588 589 /** 590 * Remove all the records about package {@code packageName} belonging to user {@code userId}. 591 * If the package is left with no records of secondary dex usage and is not used by other 592 * apps it will be removed as well. 593 * @return true if the record was found and actually deleted, 594 * false if the record doesn't exist 595 */ removeUserPackage(String packageName, int userId)596 /*package*/ boolean removeUserPackage(String packageName, int userId) { 597 synchronized (mPackageUseInfoMap) { 598 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 599 if (packageUseInfo == null) { 600 return false; 601 } 602 boolean updated = false; 603 Iterator<Map.Entry<String, DexUseInfo>> dIt = 604 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 605 while (dIt.hasNext()) { 606 DexUseInfo dexUseInfo = dIt.next().getValue(); 607 if (dexUseInfo.mOwnerUserId == userId) { 608 dIt.remove(); 609 updated = true; 610 } 611 } 612 // If no secondary dex info is left and the package is not used by other apps 613 // remove the data since it is now useless. 614 if (packageUseInfo.mDexUseInfoMap.isEmpty() 615 && !packageUseInfo.isAnyCodePathUsedByOtherApps()) { 616 mPackageUseInfoMap.remove(packageName); 617 updated = true; 618 } 619 return updated; 620 } 621 } 622 623 /** 624 * Remove the secondary dex file record belonging to the package {@code packageName} 625 * and user {@code userId}. 626 * @return true if the record was found and actually deleted, 627 * false if the record doesn't exist 628 */ removeDexFile(String packageName, String dexFile, int userId)629 /*package*/ boolean removeDexFile(String packageName, String dexFile, int userId) { 630 synchronized (mPackageUseInfoMap) { 631 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 632 if (packageUseInfo == null) { 633 return false; 634 } 635 return removeDexFile(packageUseInfo, dexFile, userId); 636 } 637 } 638 removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId)639 private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { 640 DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); 641 if (dexUseInfo == null) { 642 return false; 643 } 644 if (dexUseInfo.mOwnerUserId == userId) { 645 packageUseInfo.mDexUseInfoMap.remove(dexFile); 646 return true; 647 } 648 return false; 649 } 650 getPackageUseInfo(String packageName)651 /*package*/ PackageUseInfo getPackageUseInfo(String packageName) { 652 synchronized (mPackageUseInfoMap) { 653 PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); 654 // The useInfo contains a map for secondary dex files which could be modified 655 // concurrently after this method returns and thus outside the locking we do here. 656 // (i.e. the map is updated when new class loaders are created, which can happen anytime 657 // after this method returns) 658 // Make a defensive copy to be sure we don't get concurrent modifications. 659 return useInfo == null ? null : new PackageUseInfo(useInfo); 660 } 661 } 662 663 /** 664 * Return all packages that contain records of secondary dex files. 665 */ getAllPackagesWithSecondaryDexFiles()666 /*package*/ Set<String> getAllPackagesWithSecondaryDexFiles() { 667 Set<String> packages = new HashSet<>(); 668 synchronized (mPackageUseInfoMap) { 669 for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { 670 if (!entry.getValue().mDexUseInfoMap.isEmpty()) { 671 packages.add(entry.getKey()); 672 } 673 } 674 } 675 return packages; 676 } 677 clear()678 /* package */ void clear() { 679 synchronized (mPackageUseInfoMap) { 680 mPackageUseInfoMap.clear(); 681 } 682 } 683 684 // Creates a deep copy of the class' mPackageUseInfoMap. clonePackageUseInfoMap()685 private Map<String, PackageUseInfo> clonePackageUseInfoMap() { 686 Map<String, PackageUseInfo> clone = new HashMap<>(); 687 synchronized (mPackageUseInfoMap) { 688 for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) { 689 clone.put(e.getKey(), new PackageUseInfo(e.getValue())); 690 } 691 } 692 return clone; 693 } 694 writeBoolean(boolean bool)695 private String writeBoolean(boolean bool) { 696 return bool ? "1" : "0"; 697 } 698 readBoolean(String bool)699 private boolean readBoolean(String bool) { 700 if ("0".equals(bool)) return false; 701 if ("1".equals(bool)) return true; 702 throw new IllegalArgumentException("Unknown bool encoding: " + bool); 703 } 704 dump()705 /* package */ String dump() { 706 StringWriter sw = new StringWriter(); 707 write(sw); 708 return sw.toString(); 709 } 710 711 /** 712 * Stores data on how a package and its dex files are used. 713 */ 714 public static class PackageUseInfo { 715 // The app's code paths that are used by other apps. 716 // The key is the code path and the value is the set of loading packages. 717 private final Map<String, Set<String>> mCodePathsUsedByOtherApps; 718 // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). 719 private final Map<String, DexUseInfo> mDexUseInfoMap; 720 721 // Keeps track of whether or not this package was used by other apps before 722 // we upgraded to VERSION 4 which records the info for each code path separately. 723 // This is unwanted complexity but without it we risk to profile guide compile 724 // something that supposed to be shared. For example: 725 // 1) we determine that chrome is used by another app 726 // 2) we take an OTA which upgrades the way we keep track of usage data 727 // 3) chrome doesn't get used until the background job executes 728 // 4) as part of the backgound job we now think that chrome is not used by others 729 // and we speed-profile. 730 // 5) as a result the next time someone uses chrome it will extract from apk since 731 // the compiled code will be private. 732 private boolean mUsedByOtherAppsBeforeUpgrade; 733 PackageUseInfo()734 /*package*/ PackageUseInfo() { 735 mCodePathsUsedByOtherApps = new HashMap<>(); 736 mDexUseInfoMap = new HashMap<>(); 737 } 738 739 // Creates a deep copy of the `other`. PackageUseInfo(PackageUseInfo other)740 private PackageUseInfo(PackageUseInfo other) { 741 mCodePathsUsedByOtherApps = new HashMap<>(); 742 for (Map.Entry<String, Set<String>> e : other.mCodePathsUsedByOtherApps.entrySet()) { 743 mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); 744 } 745 746 mDexUseInfoMap = new HashMap<>(); 747 for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { 748 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); 749 } 750 } 751 mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, String owningPackageName, String loadingPackage)752 private boolean mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, 753 String owningPackageName, String loadingPackage) { 754 if (!isUsedByOtherApps) { 755 // Nothing to update if the the code path is not used by other apps. 756 return false; 757 } 758 759 boolean newCodePath = false; 760 Set<String> loadingPackages = mCodePathsUsedByOtherApps.get(codePath); 761 if (loadingPackages == null) { 762 loadingPackages = new HashSet<>(); 763 mCodePathsUsedByOtherApps.put(codePath, loadingPackages); 764 newCodePath = true; 765 } 766 boolean newLoadingPackage = loadingPackage != null 767 && !loadingPackage.equals(owningPackageName) 768 && loadingPackages.add(loadingPackage); 769 return newCodePath || newLoadingPackage; 770 } 771 isUsedByOtherApps(String codePath)772 public boolean isUsedByOtherApps(String codePath) { 773 return mCodePathsUsedByOtherApps.containsKey(codePath); 774 } 775 getDexUseInfoMap()776 public Map<String, DexUseInfo> getDexUseInfoMap() { 777 return mDexUseInfoMap; 778 } 779 getLoadingPackages(String codePath)780 public Set<String> getLoadingPackages(String codePath) { 781 return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); 782 } 783 isAnyCodePathUsedByOtherApps()784 public boolean isAnyCodePathUsedByOtherApps() { 785 return !mCodePathsUsedByOtherApps.isEmpty(); 786 } 787 788 /** 789 * Clears the usedByOtherApps markers from all code paths. 790 * Returns whether or not there was an update. 791 */ clearCodePathUsedByOtherApps()792 /*package*/ boolean clearCodePathUsedByOtherApps() { 793 // Update mUsedByOtherAppsBeforeUpgrade as well to be consistent with 794 // the new data. This is not saved to disk so we don't need to return it. 795 mUsedByOtherAppsBeforeUpgrade = true; 796 797 if (mCodePathsUsedByOtherApps.isEmpty()) { 798 return false; 799 } else { 800 mCodePathsUsedByOtherApps.clear(); 801 return true; 802 } 803 } 804 } 805 806 /** 807 * Stores data about a loaded dex files. 808 */ 809 public static class DexUseInfo { 810 private boolean mIsUsedByOtherApps; 811 private final int mOwnerUserId; 812 // The class loader context for the dex file. This encodes the class loader chain 813 // (class loader type + class path) in a format compatible to dex2oat. 814 // See {@code DexoptUtils.processContextForDexLoad}. 815 private String mClassLoaderContext; 816 // The instructions sets of the applications loading the dex file. 817 private final Set<String> mLoaderIsas; 818 // Packages who load this dex file. 819 private final Set<String> mLoadingPackages; 820 821 @VisibleForTesting DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String classLoaderContext, String loaderIsa)822 /* package */ DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, 823 String classLoaderContext, String loaderIsa) { 824 mIsUsedByOtherApps = isUsedByOtherApps; 825 mOwnerUserId = ownerUserId; 826 mClassLoaderContext = classLoaderContext; 827 mLoaderIsas = new HashSet<>(); 828 if (loaderIsa != null) { 829 mLoaderIsas.add(loaderIsa); 830 } 831 mLoadingPackages = new HashSet<>(); 832 } 833 834 // Creates a deep copy of the `other`. DexUseInfo(DexUseInfo other)835 private DexUseInfo(DexUseInfo other) { 836 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 837 mOwnerUserId = other.mOwnerUserId; 838 mClassLoaderContext = other.mClassLoaderContext; 839 mLoaderIsas = new HashSet<>(other.mLoaderIsas); 840 mLoadingPackages = new HashSet<>(other.mLoadingPackages); 841 } 842 merge(DexUseInfo dexUseInfo)843 private boolean merge(DexUseInfo dexUseInfo) { 844 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 845 mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; 846 boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); 847 boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); 848 849 String oldClassLoaderContext = mClassLoaderContext; 850 if (isUnknownOrUnsupportedContext(mClassLoaderContext)) { 851 // Can happen if we read a previous version. 852 mClassLoaderContext = dexUseInfo.mClassLoaderContext; 853 } else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext) 854 && !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { 855 // We detected a context change. 856 mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; 857 } 858 859 return updateIsas || 860 (oldIsUsedByOtherApps != mIsUsedByOtherApps) || 861 updateLoadingPackages 862 || !Objects.equals(oldClassLoaderContext, mClassLoaderContext); 863 } 864 isUnknownOrUnsupportedContext(String context)865 private static boolean isUnknownOrUnsupportedContext(String context) { 866 // TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases 867 // into UNSUPPORTED_CLASS_LOADER_CONTEXT. 868 return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context) 869 || UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context); 870 } 871 isUsedByOtherApps()872 public boolean isUsedByOtherApps() { 873 return mIsUsedByOtherApps; 874 } 875 getOwnerUserId()876 /* package */ int getOwnerUserId() { 877 return mOwnerUserId; 878 } 879 getLoaderIsas()880 public Set<String> getLoaderIsas() { 881 return mLoaderIsas; 882 } 883 getLoadingPackages()884 public Set<String> getLoadingPackages() { 885 return mLoadingPackages; 886 } 887 getClassLoaderContext()888 public String getClassLoaderContext() { return mClassLoaderContext; } 889 isUnknownClassLoaderContext()890 public boolean isUnknownClassLoaderContext() { 891 // The class loader context may be unknown if we loaded the data from a previous version 892 // which didn't save the context. 893 return isUnknownOrUnsupportedContext(mClassLoaderContext); 894 } 895 isVariableClassLoaderContext()896 public boolean isVariableClassLoaderContext() { 897 return VARIABLE_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 898 } 899 } 900 } 901