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