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