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