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