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