PackageDexUsage.java revision 6ef7f0b8c6e72e849a220513425a9fd37804af90
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    /*package*/ void maybeWriteAsync() {
182      maybeWriteAsync(null);
183    }
184
185    /*package*/ void writeNow() {
186        writeInternal(null);
187    }
188
189    @Override
190    protected void writeInternal(Void data) {
191        AtomicFile file = getFile();
192        FileOutputStream f = null;
193
194        try {
195            f = file.startWrite();
196            OutputStreamWriter osw = new OutputStreamWriter(f);
197            write(osw);
198            osw.flush();
199            file.finishWrite(f);
200        } catch (IOException e) {
201            if (f != null) {
202                file.failWrite(f);
203            }
204            Slog.e(TAG, "Failed to write usage for dex files", e);
205        }
206    }
207
208    /**
209     * File format:
210     *
211     * file_magic_version
212     * package_name_1
213     * @ loading_package_1_1, loading_package_1_2...
214     * #dex_file_path_1_1
215     * @ loading_package_1_1_1, loading_package_1_1_2...
216     * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2
217     * #dex_file_path_1_2
218     * @ loading_package_1_2_1, loading_package_1_2_2...
219     * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2
220     * ...
221     * package_name_2
222     * @ loading_package_2_1, loading_package_2_1_2...
223     * #dex_file_path_2_1
224     * @ loading_package_2_1_1, loading_package_2_1_2...
225     * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2
226     * #dex_file_path_2_2,
227     * @ loading_package_2_2_1, loading_package_2_2_2...
228     * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2
229     * ...
230    */
231    /* package */ void write(Writer out) {
232        // Make a clone to avoid locking while writing to disk.
233        Map<String, PackageUseInfo> packageUseInfoMapClone = clonePackageUseInfoMap();
234
235        FastPrintWriter fpw = new FastPrintWriter(out);
236
237        // Write the header.
238        fpw.print(PACKAGE_DEX_USAGE_VERSION_HEADER);
239        fpw.println(PACKAGE_DEX_USAGE_VERSION);
240
241        for (Map.Entry<String, PackageUseInfo> pEntry : packageUseInfoMapClone.entrySet()) {
242            // Write the package line.
243            String packageName = pEntry.getKey();
244            PackageUseInfo packageUseInfo = pEntry.getValue();
245
246            fpw.println(String.join(SPLIT_CHAR, packageName,
247                    writeBoolean(packageUseInfo.mIsUsedByOtherApps)));
248            fpw.println(LOADING_PACKAGE_CHAR +
249                    String.join(SPLIT_CHAR, packageUseInfo.mLoadingPackages));
250
251            // Write dex file lines.
252            for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) {
253                String dexPath = dEntry.getKey();
254                DexUseInfo dexUseInfo = dEntry.getValue();
255                fpw.println(DEX_LINE_CHAR + dexPath);
256                    fpw.println(LOADING_PACKAGE_CHAR +
257                            String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages));
258
259                fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId),
260                        writeBoolean(dexUseInfo.mIsUsedByOtherApps)));
261                for (String isa : dexUseInfo.mLoaderIsas) {
262                    fpw.print(SPLIT_CHAR + isa);
263                }
264                fpw.println();
265            }
266        }
267        fpw.flush();
268    }
269
270    @Override
271    protected void readInternal(Void data) {
272        AtomicFile file = getFile();
273        BufferedReader in = null;
274        try {
275            in = new BufferedReader(new InputStreamReader(file.openRead()));
276            read(in);
277        } catch (FileNotFoundException expected) {
278            // The file may not be there. E.g. When we first take the OTA with this feature.
279        } catch (IOException e) {
280            Slog.w(TAG, "Failed to parse package dex usage.", e);
281        } finally {
282            IoUtils.closeQuietly(in);
283        }
284    }
285
286    /* package */ void read(Reader reader) throws IOException {
287        Map<String, PackageUseInfo> data = new HashMap<>();
288        BufferedReader in = new BufferedReader(reader);
289        // Read header, do version check.
290        String versionLine = in.readLine();
291        int version;
292        if (versionLine == null) {
293            throw new IllegalStateException("No version line found.");
294        } else {
295            if (!versionLine.startsWith(PACKAGE_DEX_USAGE_VERSION_HEADER)) {
296                // TODO(calin): the caller is responsible to clear the file.
297                throw new IllegalStateException("Invalid version line: " + versionLine);
298            }
299            version = Integer.parseInt(
300                    versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length()));
301            if (!isSupportedVersion(version)) {
302                throw new IllegalStateException("Unexpected version: " + version);
303            }
304        }
305
306        String s;
307        String currentPackage = null;
308        PackageUseInfo currentPackageData = null;
309
310        Set<String> supportedIsas = new HashSet<>();
311        for (String abi : Build.SUPPORTED_ABIS) {
312            supportedIsas.add(VMRuntime.getInstructionSet(abi));
313        }
314        while ((s = in.readLine()) != null) {
315            if (s.startsWith(DEX_LINE_CHAR)) {
316                // This is the start of the the dex lines.
317                // We expect two lines for each dex entry:
318                // #dexPaths
319                // onwerUserId,isUsedByOtherApps,isa1,isa2
320                if (currentPackage == null) {
321                    throw new IllegalStateException(
322                        "Malformed PackageDexUsage file. Expected package line before dex line.");
323                }
324
325                // First line is the dex path.
326                String dexPath = s.substring(DEX_LINE_CHAR.length());
327
328                // In version 2 the second line contains the list of packages that loaded the file.
329                List<String> loadingPackages = maybeReadLoadingPackages(in, version);
330
331                // Next line is the dex data.
332                s = in.readLine();
333                if (s == null) {
334                    throw new IllegalStateException("Could not find dexUseInfo for line: " + s);
335                }
336
337                // We expect at least 3 elements (isUsedByOtherApps, userId, isa).
338                String[] elems = s.split(SPLIT_CHAR);
339                if (elems.length < 3) {
340                    throw new IllegalStateException("Invalid PackageDexUsage line: " + s);
341                }
342                int ownerUserId = Integer.parseInt(elems[0]);
343                boolean isUsedByOtherApps = readBoolean(elems[1]);
344                DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId);
345                dexUseInfo.mLoadingPackages.addAll(loadingPackages);
346
347                for (int i = 2; i < elems.length; i++) {
348                    String isa = elems[i];
349                    if (supportedIsas.contains(isa)) {
350                        dexUseInfo.mLoaderIsas.add(elems[i]);
351                    } else {
352                        // Should never happen unless someone crafts the file manually.
353                        // In theory it could if we drop a supported ISA after an OTA but we don't
354                        // do that.
355                        Slog.wtf(TAG, "Unsupported ISA when parsing PackageDexUsage: " + isa);
356                    }
357                }
358                if (supportedIsas.isEmpty()) {
359                    Slog.wtf(TAG, "Ignore dexPath when parsing PackageDexUsage because of " +
360                            "unsupported isas. dexPath=" + dexPath);
361                    continue;
362                }
363                currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo);
364            } else {
365                // This is a package line.
366                // We expect it to be: `packageName,isUsedByOtherApps`.
367                String[] elems = s.split(SPLIT_CHAR);
368                if (elems.length != 2) {
369                    throw new IllegalStateException("Invalid PackageDexUsage line: " + s);
370                }
371                currentPackage = elems[0];
372                currentPackageData = new PackageUseInfo();
373                currentPackageData.mIsUsedByOtherApps = readBoolean(elems[1]);
374                currentPackageData.mLoadingPackages.addAll(maybeReadLoadingPackages(in, version));
375                data.put(currentPackage, currentPackageData);
376            }
377        }
378
379        synchronized (mPackageUseInfoMap) {
380            mPackageUseInfoMap.clear();
381            mPackageUseInfoMap.putAll(data);
382        }
383    }
384
385    /**
386     * Reads the list of loading packages from the buffer {@parm in} if
387     * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}.
388     */
389    private List<String> maybeReadLoadingPackages(BufferedReader in, int version)
390            throws IOException {
391        if (version == PACKAGE_DEX_USAGE_VERSION) {
392            String line = in.readLine();
393            if (line == null) {
394                throw new IllegalStateException("Could not find the loadingPackages line.");
395            }
396            // We expect that most of the times the list of loading packages will be empty.
397            if (line.length() == LOADING_PACKAGE_CHAR.length()) {
398                return Collections.emptyList();
399            } else {
400                return Arrays.asList(
401                        line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR));
402            }
403        } else {
404            return Collections.emptyList();
405        }
406    }
407
408    /**
409     * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's
410     * not equal to {@param owningPackage}
411     */
412    private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage,
413            Set<String> loadingPackages) {
414        return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage);
415    }
416
417    private boolean isSupportedVersion(int version) {
418        return version == PACKAGE_DEX_USAGE_VERSION ||
419                version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1;
420    }
421
422    /**
423     * Syncs the existing data with the set of available packages by removing obsolete entries.
424     */
425    public void syncData(Map<String, Set<Integer>> packageToUsersMap) {
426        synchronized (mPackageUseInfoMap) {
427            Iterator<Map.Entry<String, PackageUseInfo>> pIt =
428                    mPackageUseInfoMap.entrySet().iterator();
429            while (pIt.hasNext()) {
430                Map.Entry<String, PackageUseInfo> pEntry = pIt.next();
431                String packageName = pEntry.getKey();
432                PackageUseInfo packageUseInfo = pEntry.getValue();
433                Set<Integer> users = packageToUsersMap.get(packageName);
434                if (users == null) {
435                    // The package doesn't exist anymore, remove the record.
436                    pIt.remove();
437                } else {
438                    // The package exists but we can prune the entries associated with non existing
439                    // users.
440                    Iterator<Map.Entry<String, DexUseInfo>> dIt =
441                            packageUseInfo.mDexUseInfoMap.entrySet().iterator();
442                    while (dIt.hasNext()) {
443                        DexUseInfo dexUseInfo = dIt.next().getValue();
444                        if (!users.contains(dexUseInfo.mOwnerUserId)) {
445                            // User was probably removed. Delete its dex usage info.
446                            dIt.remove();
447                        }
448                    }
449                    if (!packageUseInfo.mIsUsedByOtherApps
450                            && packageUseInfo.mDexUseInfoMap.isEmpty()) {
451                        // The package is not used by other apps and we removed all its dex files
452                        // records. Remove the entire package record as well.
453                        pIt.remove();
454                    }
455                }
456            }
457        }
458    }
459
460    /**
461     * Clears the {@code usesByOtherApps} marker for the package {@code packageName}.
462     * @return true if the package usage info was updated.
463     */
464    public boolean clearUsedByOtherApps(String packageName) {
465        synchronized (mPackageUseInfoMap) {
466            PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
467            if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) {
468                return false;
469            }
470            packageUseInfo.mIsUsedByOtherApps = false;
471            return true;
472        }
473    }
474
475    /**
476     * Remove the usage data associated with package {@code packageName}.
477     * @return true if the package usage was found and removed successfully.
478     */
479    public boolean removePackage(String packageName) {
480        synchronized (mPackageUseInfoMap) {
481            return mPackageUseInfoMap.remove(packageName) != null;
482        }
483    }
484
485    /**
486     * Remove all the records about package {@code packageName} belonging to user {@code userId}.
487     * If the package is left with no records of secondary dex usage and is not used by other
488     * apps it will be removed as well.
489     * @return true if the record was found and actually deleted,
490     *         false if the record doesn't exist
491     */
492    public boolean removeUserPackage(String packageName, int userId) {
493        synchronized (mPackageUseInfoMap) {
494            PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
495            if (packageUseInfo == null) {
496                return false;
497            }
498            boolean updated = false;
499            Iterator<Map.Entry<String, DexUseInfo>> dIt =
500                            packageUseInfo.mDexUseInfoMap.entrySet().iterator();
501            while (dIt.hasNext()) {
502                DexUseInfo dexUseInfo = dIt.next().getValue();
503                if (dexUseInfo.mOwnerUserId == userId) {
504                    dIt.remove();
505                    updated = true;
506                }
507            }
508            // If no secondary dex info is left and the package is not used by other apps
509            // remove the data since it is now useless.
510            if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) {
511                mPackageUseInfoMap.remove(packageName);
512                updated = true;
513            }
514            return updated;
515        }
516    }
517
518    /**
519     * Remove the secondary dex file record belonging to the package {@code packageName}
520     * and user {@code userId}.
521     * @return true if the record was found and actually deleted,
522     *         false if the record doesn't exist
523     */
524    public boolean removeDexFile(String packageName, String dexFile, int userId) {
525        synchronized (mPackageUseInfoMap) {
526            PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
527            if (packageUseInfo == null) {
528                return false;
529            }
530            return removeDexFile(packageUseInfo, dexFile, userId);
531        }
532    }
533
534    private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) {
535        DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile);
536        if (dexUseInfo == null) {
537            return false;
538        }
539        if (dexUseInfo.mOwnerUserId == userId) {
540            packageUseInfo.mDexUseInfoMap.remove(dexFile);
541            return true;
542        }
543        return false;
544    }
545
546    public PackageUseInfo getPackageUseInfo(String packageName) {
547        synchronized (mPackageUseInfoMap) {
548            PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName);
549            // The useInfo contains a map for secondary dex files which could be modified
550            // concurrently after this method returns and thus outside the locking we do here.
551            // (i.e. the map is updated when new class loaders are created, which can happen anytime
552            // after this method returns)
553            // Make a defensive copy to be sure we don't get concurrent modifications.
554            return useInfo == null ? null : new PackageUseInfo(useInfo);
555        }
556    }
557
558    /**
559     * Return all packages that contain records of secondary dex files.
560     */
561    public Set<String> getAllPackagesWithSecondaryDexFiles() {
562        Set<String> packages = new HashSet<>();
563        synchronized (mPackageUseInfoMap) {
564            for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) {
565                if (!entry.getValue().mDexUseInfoMap.isEmpty()) {
566                    packages.add(entry.getKey());
567                }
568            }
569        }
570        return packages;
571    }
572
573    public void clear() {
574        synchronized (mPackageUseInfoMap) {
575            mPackageUseInfoMap.clear();
576        }
577    }
578    // Creates a deep copy of the class' mPackageUseInfoMap.
579    private Map<String, PackageUseInfo> clonePackageUseInfoMap() {
580        Map<String, PackageUseInfo> clone = new HashMap<>();
581        synchronized (mPackageUseInfoMap) {
582            for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) {
583                clone.put(e.getKey(), new PackageUseInfo(e.getValue()));
584            }
585        }
586        return clone;
587    }
588
589    private String writeBoolean(boolean bool) {
590        return bool ? "1" : "0";
591    }
592
593    private boolean readBoolean(String bool) {
594        if ("0".equals(bool)) return false;
595        if ("1".equals(bool)) return true;
596        throw new IllegalArgumentException("Unknown bool encoding: " + bool);
597    }
598
599    private boolean contains(int[] array, int elem) {
600        for (int i = 0; i < array.length; i++) {
601            if (elem == array[i]) {
602                return true;
603            }
604        }
605        return false;
606    }
607
608    public String dump() {
609        StringWriter sw = new StringWriter();
610        write(sw);
611        return sw.toString();
612    }
613
614    /**
615     * Stores data on how a package and its dex files are used.
616     */
617    public static class PackageUseInfo {
618        // This flag is for the primary and split apks. It is set to true whenever one of them
619        // is loaded by another app.
620        private boolean mIsUsedByOtherApps;
621        // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa).
622        private final Map<String, DexUseInfo> mDexUseInfoMap;
623        // Packages who load this dex file.
624        private final Set<String> mLoadingPackages;
625
626        public PackageUseInfo() {
627            mIsUsedByOtherApps = false;
628            mDexUseInfoMap = new HashMap<>();
629            mLoadingPackages = new HashSet<>();
630        }
631
632        // Creates a deep copy of the `other`.
633        public PackageUseInfo(PackageUseInfo other) {
634            mIsUsedByOtherApps = other.mIsUsedByOtherApps;
635            mDexUseInfoMap = new HashMap<>();
636            for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) {
637                mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue()));
638            }
639            mLoadingPackages = new HashSet<>(other.mLoadingPackages);
640        }
641
642        private boolean merge(boolean isUsedByOtherApps) {
643            boolean oldIsUsedByOtherApps = mIsUsedByOtherApps;
644            mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps;
645            return oldIsUsedByOtherApps != this.mIsUsedByOtherApps;
646        }
647
648        public boolean isUsedByOtherApps() {
649            return mIsUsedByOtherApps;
650        }
651
652        public Map<String, DexUseInfo> getDexUseInfoMap() {
653            return mDexUseInfoMap;
654        }
655
656        public Set<String> getLoadingPackages() {
657            return mLoadingPackages;
658        }
659    }
660
661    /**
662     * Stores data about a loaded dex files.
663     */
664    public static class DexUseInfo {
665        private boolean mIsUsedByOtherApps;
666        private final int mOwnerUserId;
667        private final Set<String> mLoaderIsas;
668        // Packages who load this dex file.
669        private final Set<String> mLoadingPackages;
670
671        public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) {
672            this(isUsedByOtherApps, ownerUserId, null);
673        }
674
675        public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) {
676            mIsUsedByOtherApps = isUsedByOtherApps;
677            mOwnerUserId = ownerUserId;
678            mLoaderIsas = new HashSet<>();
679            if (loaderIsa != null) {
680                mLoaderIsas.add(loaderIsa);
681            }
682            mLoadingPackages = new HashSet<>();
683        }
684
685        // Creates a deep copy of the `other`.
686        public DexUseInfo(DexUseInfo other) {
687            mIsUsedByOtherApps = other.mIsUsedByOtherApps;
688            mOwnerUserId = other.mOwnerUserId;
689            mLoaderIsas = new HashSet<>(other.mLoaderIsas);
690            mLoadingPackages = new HashSet<>(other.mLoadingPackages);
691        }
692
693        private boolean merge(DexUseInfo dexUseInfo) {
694            boolean oldIsUsedByOtherApps = mIsUsedByOtherApps;
695            mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps;
696            boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas);
697            boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
698            return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps) ||
699                    updateLoadingPackages;
700        }
701
702        public boolean isUsedByOtherApps() {
703            return mIsUsedByOtherApps;
704        }
705
706        public int getOwnerUserId() {
707            return mOwnerUserId;
708        }
709
710        public Set<String> getLoaderIsas() {
711            return mLoaderIsas;
712        }
713
714        public Set<String> getLoadingPackages() {
715            return mLoadingPackages;
716        }
717    }
718}
719