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