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