PackageDexUsage.java revision 52a452cf685c56dc6872dbb19e822736484f672f
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.pm.dex;
18
19import android.util.AtomicFile;
20import android.util.Slog;
21import android.os.Build;
22
23import com.android.internal.annotations.GuardedBy;
24import com.android.internal.util.FastPrintWriter;
25import com.android.server.pm.AbstractStatsBase;
26import com.android.server.pm.PackageManagerServiceUtils;
27
28import java.io.BufferedReader;
29import java.io.FileNotFoundException;
30import java.io.FileOutputStream;
31import java.io.InputStreamReader;
32import java.io.IOException;
33import java.io.OutputStreamWriter;
34import java.io.Reader;
35import java.io.StringWriter;
36import java.io.Writer;
37import java.util.Arrays;
38import java.util.Collection;
39import java.util.Collections;
40import java.util.Iterator;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Map;
45import java.util.Set;
46
47import dalvik.system.VMRuntime;
48import libcore.io.IoUtils;
49import libcore.util.Objects;
50
51/**
52 * Stat file which store usage information about dex files.
53 */
54public 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.equal(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.equal(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