PackageDexUsage.java revision 3bec94d78b0a66c4fa5cebd851ea33bcc51916b0
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 public void maybeWriteAsync() { 202 maybeWriteAsync((Void) null); 203 } 204 205 @Override 206 protected void writeInternal(Void data) { 207 AtomicFile file = getFile(); 208 FileOutputStream f = null; 209 210 try { 211 f = file.startWrite(); 212 OutputStreamWriter osw = new OutputStreamWriter(f); 213 write(osw); 214 osw.flush(); 215 file.finishWrite(f); 216 } catch (IOException e) { 217 if (f != null) { 218 file.failWrite(f); 219 } 220 Slog.e(TAG, "Failed to write usage for dex files", e); 221 } 222 } 223 224 /** 225 * File format: 226 * 227 * file_magic_version 228 * package_name_1 229 * @ loading_package_1_1, loading_package_1_2... 230 * #dex_file_path_1_1 231 * @ loading_package_1_1_1, loading_package_1_1_2... 232 * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 233 * #dex_file_path_1_2 234 * @ loading_package_1_2_1, loading_package_1_2_2... 235 * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 236 * ... 237 * package_name_2 238 * @ loading_package_2_1, loading_package_2_1_2... 239 * #dex_file_path_2_1 240 * @ loading_package_2_1_1, loading_package_2_1_2... 241 * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2 242 * #dex_file_path_2_2, 243 * @ loading_package_2_2_1, loading_package_2_2_2... 244 * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2 245 * ... 246 */ 247 /* package */ void write(Writer out) { 248 // Make a clone to avoid locking while writing to disk. 249 Map<String, PackageUseInfo> packageUseInfoMapClone = clonePackageUseInfoMap(); 250 251 FastPrintWriter fpw = new FastPrintWriter(out); 252 253 // Write the header. 254 fpw.print(PACKAGE_DEX_USAGE_VERSION_HEADER); 255 fpw.println(PACKAGE_DEX_USAGE_VERSION); 256 257 for (Map.Entry<String, PackageUseInfo> pEntry : packageUseInfoMapClone.entrySet()) { 258 // Write the package line. 259 String packageName = pEntry.getKey(); 260 PackageUseInfo packageUseInfo = pEntry.getValue(); 261 262 fpw.println(String.join(SPLIT_CHAR, packageName, 263 writeBoolean(packageUseInfo.mIsUsedByOtherApps))); 264 fpw.println(LOADING_PACKAGE_CHAR + 265 String.join(SPLIT_CHAR, packageUseInfo.mLoadingPackages)); 266 267 // Write dex file lines. 268 for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { 269 String dexPath = dEntry.getKey(); 270 DexUseInfo dexUseInfo = dEntry.getValue(); 271 fpw.println(DEX_LINE_CHAR + dexPath); 272 fpw.println(LOADING_PACKAGE_CHAR + 273 String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages)); 274 fpw.println(dexUseInfo.getClassLoaderContext()); 275 276 fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), 277 writeBoolean(dexUseInfo.mIsUsedByOtherApps))); 278 for (String isa : dexUseInfo.mLoaderIsas) { 279 fpw.print(SPLIT_CHAR + isa); 280 } 281 fpw.println(); 282 } 283 } 284 fpw.flush(); 285 } 286 287 @Override 288 protected void readInternal(Void data) { 289 AtomicFile file = getFile(); 290 BufferedReader in = null; 291 try { 292 in = new BufferedReader(new InputStreamReader(file.openRead())); 293 read(in); 294 } catch (FileNotFoundException expected) { 295 // The file may not be there. E.g. When we first take the OTA with this feature. 296 } catch (IOException e) { 297 Slog.w(TAG, "Failed to parse package dex usage.", e); 298 } finally { 299 IoUtils.closeQuietly(in); 300 } 301 } 302 303 /* package */ void read(Reader reader) throws IOException { 304 Map<String, PackageUseInfo> data = new HashMap<>(); 305 BufferedReader in = new BufferedReader(reader); 306 // Read header, do version check. 307 String versionLine = in.readLine(); 308 int version; 309 if (versionLine == null) { 310 throw new IllegalStateException("No version line found."); 311 } else { 312 if (!versionLine.startsWith(PACKAGE_DEX_USAGE_VERSION_HEADER)) { 313 // TODO(calin): the caller is responsible to clear the file. 314 throw new IllegalStateException("Invalid version line: " + versionLine); 315 } 316 version = Integer.parseInt( 317 versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); 318 if (!isSupportedVersion(version)) { 319 throw new IllegalStateException("Unexpected version: " + version); 320 } 321 } 322 323 String s; 324 String currentPackage = null; 325 PackageUseInfo currentPackageData = null; 326 327 Set<String> supportedIsas = new HashSet<>(); 328 for (String abi : Build.SUPPORTED_ABIS) { 329 supportedIsas.add(VMRuntime.getInstructionSet(abi)); 330 } 331 while ((s = in.readLine()) != null) { 332 if (s.startsWith(DEX_LINE_CHAR)) { 333 // This is the start of the the dex lines. 334 // We expect 4 lines for each dex entry: 335 // #dexPaths 336 // @loading_package_1,loading_package_2,... 337 // class_loader_context 338 // onwerUserId,isUsedByOtherApps,isa1,isa2 339 if (currentPackage == null) { 340 throw new IllegalStateException( 341 "Malformed PackageDexUsage file. Expected package line before dex line."); 342 } 343 344 // First line is the dex path. 345 String dexPath = s.substring(DEX_LINE_CHAR.length()); 346 347 // In version 2 the second line contains the list of packages that loaded the file. 348 List<String> loadingPackages = maybeReadLoadingPackages(in, version); 349 // In version 3 the third line contains the class loader context. 350 String classLoaderContext = maybeReadClassLoaderContext(in, version); 351 352 // Next line is the dex data. 353 s = in.readLine(); 354 if (s == null) { 355 throw new IllegalStateException("Could not find dexUseInfo line"); 356 } 357 358 // We expect at least 3 elements (isUsedByOtherApps, userId, isa). 359 String[] elems = s.split(SPLIT_CHAR); 360 if (elems.length < 3) { 361 throw new IllegalStateException("Invalid PackageDexUsage line: " + s); 362 } 363 int ownerUserId = Integer.parseInt(elems[0]); 364 boolean isUsedByOtherApps = readBoolean(elems[1]); 365 DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId, 366 classLoaderContext, /*isa*/ null); 367 dexUseInfo.mLoadingPackages.addAll(loadingPackages); 368 for (int i = 2; i < elems.length; i++) { 369 String isa = elems[i]; 370 if (supportedIsas.contains(isa)) { 371 dexUseInfo.mLoaderIsas.add(elems[i]); 372 } else { 373 // Should never happen unless someone crafts the file manually. 374 // In theory it could if we drop a supported ISA after an OTA but we don't 375 // do that. 376 Slog.wtf(TAG, "Unsupported ISA when parsing PackageDexUsage: " + isa); 377 } 378 } 379 if (supportedIsas.isEmpty()) { 380 Slog.wtf(TAG, "Ignore dexPath when parsing PackageDexUsage because of " + 381 "unsupported isas. dexPath=" + dexPath); 382 continue; 383 } 384 currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); 385 } else { 386 // This is a package line. 387 // We expect it to be: `packageName,isUsedByOtherApps`. 388 String[] elems = s.split(SPLIT_CHAR); 389 if (elems.length != 2) { 390 throw new IllegalStateException("Invalid PackageDexUsage line: " + s); 391 } 392 currentPackage = elems[0]; 393 currentPackageData = new PackageUseInfo(); 394 currentPackageData.mIsUsedByOtherApps = readBoolean(elems[1]); 395 currentPackageData.mLoadingPackages.addAll(maybeReadLoadingPackages(in, version)); 396 data.put(currentPackage, currentPackageData); 397 } 398 } 399 400 synchronized (mPackageUseInfoMap) { 401 mPackageUseInfoMap.clear(); 402 mPackageUseInfoMap.putAll(data); 403 } 404 } 405 406 /** 407 * Reads the class loader context encoding from the buffer {@code in} if 408 * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}. 409 */ 410 private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { 411 String context = null; 412 if (version == PACKAGE_DEX_USAGE_VERSION) { 413 context = in.readLine(); 414 if (context == null) { 415 throw new IllegalStateException("Could not find the classLoaderContext line."); 416 } 417 } 418 // The context might be empty if we didn't have the chance to update it after a version 419 // upgrade. In this case return the special marker so that we recognize this is an unknown 420 // context. 421 return context == null ? UNKNOWN_CLASS_LOADER_CONTEXT : context; 422 } 423 424 /** 425 * Reads the list of loading packages from the buffer {@code in} if 426 * {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}. 427 */ 428 private List<String> maybeReadLoadingPackages(BufferedReader in, int version) 429 throws IOException { 430 if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { 431 String line = in.readLine(); 432 if (line == null) { 433 throw new IllegalStateException("Could not find the loadingPackages line."); 434 } 435 // We expect that most of the times the list of loading packages will be empty. 436 if (line.length() == LOADING_PACKAGE_CHAR.length()) { 437 return Collections.emptyList(); 438 } else { 439 return Arrays.asList( 440 line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); 441 } 442 } else { 443 return Collections.emptyList(); 444 } 445 } 446 447 /** 448 * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's 449 * not equal to {@param owningPackage} 450 */ 451 private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage, 452 Set<String> loadingPackages) { 453 return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage); 454 } 455 456 private boolean isSupportedVersion(int version) { 457 return version == PACKAGE_DEX_USAGE_VERSION || 458 version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1; 459 } 460 461 /** 462 * Syncs the existing data with the set of available packages by removing obsolete entries. 463 */ 464 public void syncData(Map<String, Set<Integer>> packageToUsersMap) { 465 synchronized (mPackageUseInfoMap) { 466 Iterator<Map.Entry<String, PackageUseInfo>> pIt = 467 mPackageUseInfoMap.entrySet().iterator(); 468 while (pIt.hasNext()) { 469 Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); 470 String packageName = pEntry.getKey(); 471 PackageUseInfo packageUseInfo = pEntry.getValue(); 472 Set<Integer> users = packageToUsersMap.get(packageName); 473 if (users == null) { 474 // The package doesn't exist anymore, remove the record. 475 pIt.remove(); 476 } else { 477 // The package exists but we can prune the entries associated with non existing 478 // users. 479 Iterator<Map.Entry<String, DexUseInfo>> dIt = 480 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 481 while (dIt.hasNext()) { 482 DexUseInfo dexUseInfo = dIt.next().getValue(); 483 if (!users.contains(dexUseInfo.mOwnerUserId)) { 484 // User was probably removed. Delete its dex usage info. 485 dIt.remove(); 486 } 487 } 488 if (!packageUseInfo.mIsUsedByOtherApps 489 && packageUseInfo.mDexUseInfoMap.isEmpty()) { 490 // The package is not used by other apps and we removed all its dex files 491 // records. Remove the entire package record as well. 492 pIt.remove(); 493 } 494 } 495 } 496 } 497 } 498 499 /** 500 * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. 501 * @return true if the package usage info was updated. 502 */ 503 public boolean clearUsedByOtherApps(String packageName) { 504 synchronized (mPackageUseInfoMap) { 505 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 506 if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) { 507 return false; 508 } 509 packageUseInfo.mIsUsedByOtherApps = false; 510 return true; 511 } 512 } 513 514 /** 515 * Remove the usage data associated with package {@code packageName}. 516 * @return true if the package usage was found and removed successfully. 517 */ 518 public boolean removePackage(String packageName) { 519 synchronized (mPackageUseInfoMap) { 520 return mPackageUseInfoMap.remove(packageName) != null; 521 } 522 } 523 524 /** 525 * Remove all the records about package {@code packageName} belonging to user {@code userId}. 526 * If the package is left with no records of secondary dex usage and is not used by other 527 * apps it will be removed as well. 528 * @return true if the record was found and actually deleted, 529 * false if the record doesn't exist 530 */ 531 public boolean removeUserPackage(String packageName, int userId) { 532 synchronized (mPackageUseInfoMap) { 533 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 534 if (packageUseInfo == null) { 535 return false; 536 } 537 boolean updated = false; 538 Iterator<Map.Entry<String, DexUseInfo>> dIt = 539 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 540 while (dIt.hasNext()) { 541 DexUseInfo dexUseInfo = dIt.next().getValue(); 542 if (dexUseInfo.mOwnerUserId == userId) { 543 dIt.remove(); 544 updated = true; 545 } 546 } 547 // If no secondary dex info is left and the package is not used by other apps 548 // remove the data since it is now useless. 549 if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) { 550 mPackageUseInfoMap.remove(packageName); 551 updated = true; 552 } 553 return updated; 554 } 555 } 556 557 /** 558 * Remove the secondary dex file record belonging to the package {@code packageName} 559 * and user {@code userId}. 560 * @return true if the record was found and actually deleted, 561 * false if the record doesn't exist 562 */ 563 public boolean removeDexFile(String packageName, String dexFile, int userId) { 564 synchronized (mPackageUseInfoMap) { 565 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 566 if (packageUseInfo == null) { 567 return false; 568 } 569 return removeDexFile(packageUseInfo, dexFile, userId); 570 } 571 } 572 573 private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { 574 DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); 575 if (dexUseInfo == null) { 576 return false; 577 } 578 if (dexUseInfo.mOwnerUserId == userId) { 579 packageUseInfo.mDexUseInfoMap.remove(dexFile); 580 return true; 581 } 582 return false; 583 } 584 585 public PackageUseInfo getPackageUseInfo(String packageName) { 586 synchronized (mPackageUseInfoMap) { 587 PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); 588 // The useInfo contains a map for secondary dex files which could be modified 589 // concurrently after this method returns and thus outside the locking we do here. 590 // (i.e. the map is updated when new class loaders are created, which can happen anytime 591 // after this method returns) 592 // Make a defensive copy to be sure we don't get concurrent modifications. 593 return useInfo == null ? null : new PackageUseInfo(useInfo); 594 } 595 } 596 597 /** 598 * Return all packages that contain records of secondary dex files. 599 */ 600 public Set<String> getAllPackagesWithSecondaryDexFiles() { 601 Set<String> packages = new HashSet<>(); 602 synchronized (mPackageUseInfoMap) { 603 for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { 604 if (!entry.getValue().mDexUseInfoMap.isEmpty()) { 605 packages.add(entry.getKey()); 606 } 607 } 608 } 609 return packages; 610 } 611 612 public void clear() { 613 synchronized (mPackageUseInfoMap) { 614 mPackageUseInfoMap.clear(); 615 } 616 } 617 // Creates a deep copy of the class' mPackageUseInfoMap. 618 private Map<String, PackageUseInfo> clonePackageUseInfoMap() { 619 Map<String, PackageUseInfo> clone = new HashMap<>(); 620 synchronized (mPackageUseInfoMap) { 621 for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) { 622 clone.put(e.getKey(), new PackageUseInfo(e.getValue())); 623 } 624 } 625 return clone; 626 } 627 628 private String writeBoolean(boolean bool) { 629 return bool ? "1" : "0"; 630 } 631 632 private boolean readBoolean(String bool) { 633 if ("0".equals(bool)) return false; 634 if ("1".equals(bool)) return true; 635 throw new IllegalArgumentException("Unknown bool encoding: " + bool); 636 } 637 638 private boolean contains(int[] array, int elem) { 639 for (int i = 0; i < array.length; i++) { 640 if (elem == array[i]) { 641 return true; 642 } 643 } 644 return false; 645 } 646 647 public String dump() { 648 StringWriter sw = new StringWriter(); 649 write(sw); 650 return sw.toString(); 651 } 652 653 /** 654 * Stores data on how a package and its dex files are used. 655 */ 656 public static class PackageUseInfo { 657 // This flag is for the primary and split apks. It is set to true whenever one of them 658 // is loaded by another app. 659 private boolean mIsUsedByOtherApps; 660 // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). 661 private final Map<String, DexUseInfo> mDexUseInfoMap; 662 // Packages who load this dex file. 663 private final Set<String> mLoadingPackages; 664 665 public PackageUseInfo() { 666 mIsUsedByOtherApps = false; 667 mDexUseInfoMap = new HashMap<>(); 668 mLoadingPackages = new HashSet<>(); 669 } 670 671 // Creates a deep copy of the `other`. 672 public PackageUseInfo(PackageUseInfo other) { 673 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 674 mDexUseInfoMap = new HashMap<>(); 675 for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { 676 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); 677 } 678 mLoadingPackages = new HashSet<>(other.mLoadingPackages); 679 } 680 681 private boolean merge(boolean isUsedByOtherApps) { 682 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 683 mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps; 684 return oldIsUsedByOtherApps != this.mIsUsedByOtherApps; 685 } 686 687 public boolean isUsedByOtherApps() { 688 return mIsUsedByOtherApps; 689 } 690 691 public Map<String, DexUseInfo> getDexUseInfoMap() { 692 return mDexUseInfoMap; 693 } 694 695 public Set<String> getLoadingPackages() { 696 return mLoadingPackages; 697 } 698 } 699 700 /** 701 * Stores data about a loaded dex files. 702 */ 703 public static class DexUseInfo { 704 private boolean mIsUsedByOtherApps; 705 private final int mOwnerUserId; 706 // The class loader context for the dex file. This encodes the class loader chain 707 // (class loader type + class path) in a format compatible to dex2oat. 708 // See {@code DexoptUtils.processContextForDexLoad}. 709 private String mClassLoaderContext; 710 // The instructions sets of the applications loading the dex file. 711 private final Set<String> mLoaderIsas; 712 // Packages who load this dex file. 713 private final Set<String> mLoadingPackages; 714 715 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String classLoaderContext, 716 String loaderIsa) { 717 mIsUsedByOtherApps = isUsedByOtherApps; 718 mOwnerUserId = ownerUserId; 719 mClassLoaderContext = classLoaderContext; 720 mLoaderIsas = new HashSet<>(); 721 if (loaderIsa != null) { 722 mLoaderIsas.add(loaderIsa); 723 } 724 mLoadingPackages = new HashSet<>(); 725 } 726 727 // Creates a deep copy of the `other`. 728 public DexUseInfo(DexUseInfo other) { 729 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 730 mOwnerUserId = other.mOwnerUserId; 731 mClassLoaderContext = other.mClassLoaderContext; 732 mLoaderIsas = new HashSet<>(other.mLoaderIsas); 733 mLoadingPackages = new HashSet<>(other.mLoadingPackages); 734 } 735 736 private boolean merge(DexUseInfo dexUseInfo) { 737 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 738 mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; 739 boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); 740 boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); 741 742 String oldClassLoaderContext = mClassLoaderContext; 743 if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) { 744 // Can happen if we read a previous version. 745 mClassLoaderContext = dexUseInfo.mClassLoaderContext; 746 } else if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(dexUseInfo.mClassLoaderContext)) { 747 // We detected an unsupported context. 748 mClassLoaderContext = UNSUPPORTED_CLASS_LOADER_CONTEXT; 749 } else if (!UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext) && 750 !Objects.equal(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { 751 // We detected a context change. 752 mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; 753 } 754 755 return updateIsas || 756 (oldIsUsedByOtherApps != mIsUsedByOtherApps) || 757 updateLoadingPackages 758 || !Objects.equal(oldClassLoaderContext, mClassLoaderContext); 759 } 760 761 public boolean isUsedByOtherApps() { 762 return mIsUsedByOtherApps; 763 } 764 765 public int getOwnerUserId() { 766 return mOwnerUserId; 767 } 768 769 public Set<String> getLoaderIsas() { 770 return mLoaderIsas; 771 } 772 773 public Set<String> getLoadingPackages() { 774 return mLoadingPackages; 775 } 776 777 public String getClassLoaderContext() { return mClassLoaderContext; } 778 779 public boolean isUnsupportedClassLoaderContext() { 780 return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 781 } 782 783 public boolean isUnknownClassLoaderContext() { 784 // The class loader context may be unknown if we loaded the data from a previous version 785 // which didn't save the context. 786 return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 787 } 788 789 public boolean isVariableClassLoaderContext() { 790 return VARIABLE_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 791 } 792 } 793} 794