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