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