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