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