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.Set; 46 47 import dalvik.system.VMRuntime; 48 import libcore.io.IoUtils; 49 import libcore.util.Objects; 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 PackageDexUsage()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 */ record(String owningPackageName, String dexPath, int ownerUserId, String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String loadingPackageName, String classLoaderContext)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 */ read()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 */ maybeWriteAsync()204 /*package*/ void maybeWriteAsync() { 205 maybeWriteAsync(null); 206 } 207 writeNow()208 /*package*/ void writeNow() { 209 writeInternal(null); 210 } 211 212 @Override writeInternal(Void data)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 */ write(Writer out)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 readInternal(Void data)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 read(Reader reader)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 */ maybeReadClassLoaderContext(BufferedReader in, int version)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 */ maybeReadLoadingPackages(BufferedReader in, int version)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 */ maybeAddLoadingPackage(String owningPackage, String loadingPackage, Set<String> loadingPackages)476 private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage, 477 Set<String> loadingPackages) { 478 return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage); 479 } 480 isSupportedVersion(int version)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 */ syncData(Map<String, Set<Integer>> packageToUsersMap, Map<String, Set<String>> packageToCodePaths)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 */ clearUsedByOtherApps(String packageName)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 */ removePackage(String packageName)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 */ removeUserPackage(String packageName, int userId)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 */ removeDexFile(String packageName, String dexFile, int userId)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 removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId)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 getPackageUseInfo(String packageName)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 */ getAllPackagesWithSecondaryDexFiles()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 clear()656 public void clear() { 657 synchronized (mPackageUseInfoMap) { 658 mPackageUseInfoMap.clear(); 659 } 660 } 661 // Creates a deep copy of the class' mPackageUseInfoMap. clonePackageUseInfoMap()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 writeBoolean(boolean bool)672 private String writeBoolean(boolean bool) { 673 return bool ? "1" : "0"; 674 } 675 readBoolean(String bool)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 dump()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 PackageUseInfo()711 public PackageUseInfo() { 712 mCodePathsUsedByOtherApps = new HashMap<>(); 713 mDexUseInfoMap = new HashMap<>(); 714 } 715 716 // Creates a deep copy of the `other`. PackageUseInfo(PackageUseInfo 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 mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, String owningPackageName, String loadingPackage)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 isUsedByOtherApps(String codePath)749 public boolean isUsedByOtherApps(String codePath) { 750 return mCodePathsUsedByOtherApps.containsKey(codePath); 751 } 752 getDexUseInfoMap()753 public Map<String, DexUseInfo> getDexUseInfoMap() { 754 return mDexUseInfoMap; 755 } 756 getLoadingPackages(String codePath)757 public Set<String> getLoadingPackages(String codePath) { 758 return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); 759 } 760 isAnyCodePathUsedByOtherApps()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 */ clearCodePathUsedByOtherApps()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 DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String classLoaderContext, String loaderIsa)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`. DexUseInfo(DexUseInfo 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 merge(DexUseInfo dexUseInfo)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.equal(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.equal(oldClassLoaderContext, mClassLoaderContext); 842 } 843 isUsedByOtherApps()844 public boolean isUsedByOtherApps() { 845 return mIsUsedByOtherApps; 846 } 847 getOwnerUserId()848 public int getOwnerUserId() { 849 return mOwnerUserId; 850 } 851 getLoaderIsas()852 public Set<String> getLoaderIsas() { 853 return mLoaderIsas; 854 } 855 getLoadingPackages()856 public Set<String> getLoadingPackages() { 857 return mLoadingPackages; 858 } 859 getClassLoaderContext()860 public String getClassLoaderContext() { return mClassLoaderContext; } 861 isUnsupportedClassLoaderContext()862 public boolean isUnsupportedClassLoaderContext() { 863 return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 864 } 865 isUnknownClassLoaderContext()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 isVariableClassLoaderContext()872 public boolean isVariableClassLoaderContext() { 873 return VARIABLE_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 874 } 875 } 876 } 877