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