OSDN Git Service

Record dex files users in the dex-usage list
authorCalin Juravle <calin@google.com>
Sat, 4 Feb 2017 00:55:49 +0000 (16:55 -0800)
committerAndreas Gampe <agampe@google.com>
Wed, 1 Nov 2017 20:07:34 +0000 (13:07 -0700)
Add the users of the dex files in the package-dex-usage.list. This will
provide more data on why a package is marked as shared and not optimized
using profiles.

(cherry picked from commit 535a4753e313bdc2ae3e8be9f50606b82edcce0c)

Test: runtest -x .../DexManagerTests.java
      users of the dex files are recorded in package-dex-usage.list

Bug: 63778376
Merged-In: I329bc929b17fa0afe1531f3e6879f6160157a787
Change-Id: I329bc929b17fa0afe1531f3e6879f6160157a787

services/core/java/com/android/server/pm/dex/DexManager.java
services/core/java/com/android/server/pm/dex/PackageDexUsage.java
services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java

index 0d4df4d..3d2d483 100644 (file)
@@ -148,7 +148,8 @@ public class DexManager {
                 // or UsedBytOtherApps), record will return true and we trigger an async write
                 // to disk to make sure we don't loose the data in case of a reboot.
                 if (mPackageDexUsage.record(searchResult.mOwningPackageName,
-                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) {
+                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
+                        loadingAppInfo.packageName)) {
                     mPackageDexUsage.maybeWriteAsync();
                 }
             } else {
@@ -455,7 +456,8 @@ public class DexManager {
         for (String isa : getAppDexInstructionSets(info)) {
             isas.add(isa);
             boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
-                dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false);
+                    dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
+                    searchResult.mOwningPackageName);
             update |= newUpdate;
         }
         if (update) {
index 8a66f12..f7dd174 100644 (file)
@@ -35,14 +35,19 @@ import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import dalvik.system.VMRuntime;
 import libcore.io.IoUtils;
+import libcore.util.Objects;
 
 /**
  * Stat file which store usage information about dex files.
@@ -50,19 +55,23 @@ import libcore.io.IoUtils;
 public class PackageDexUsage extends AbstractStatsBase<Void> {
     private final static String TAG = "PackageDexUsage";
 
-    private final static int PACKAGE_DEX_USAGE_VERSION = 1;
+    // The last version update: add the list of packages that load the dex files.
+    private final static int PACKAGE_DEX_USAGE_VERSION = 2;
+    // We support VERSION 1 to ensure that the usage list remains valid cross OTAs.
+    private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1;
+
     private final static String PACKAGE_DEX_USAGE_VERSION_HEADER =
             "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__";
 
     private final static String SPLIT_CHAR = ",";
     private final static String DEX_LINE_CHAR = "#";
-
+    private final static String LOADING_PACKAGE_CHAR = "@";
     // Map which structures the information we have on a package.
     // Maps package name to package data (which stores info about UsedByOtherApps and
     // secondary dex files.).
     // Access to this map needs synchronized.
     @GuardedBy("mPackageUseInfoMap")
-    private Map<String, PackageUseInfo> mPackageUseInfoMap;
+    private final Map<String, PackageUseInfo> mPackageUseInfoMap;
 
     public PackageDexUsage() {
         super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false);
@@ -75,18 +84,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
      * Note this is called when apps load dex files and as such it should return
      * as fast as possible.
      *
-     * @param loadingPackage the package performing the load
+     * @param owningPackageName the package owning the dex path
      * @param dexPath the path of the dex files being loaded
      * @param ownerUserId the user id which runs the code loading the dex files
      * @param loaderIsa the ISA of the app loading the dex files
      * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package
      * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates
      *        the file is either primary or a split. False indicates the file is secondary dex.
+     * @param loadingPackageName the package performing the load. Recorded only if it is different
+     *        than {@param owningPackageName}.
      * @return true if the dex load constitutes new information, or false if this information
      *         has been seen before.
      */
     public boolean record(String owningPackageName, String dexPath, int ownerUserId,
-            String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) {
+            String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit,
+            String loadingPackageName) {
         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
             throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported");
         }
@@ -100,11 +112,15 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                     // We do not need to record the loaderIsa or the owner because we compile
                     // primaries for all users and all ISAs.
                     packageUseInfo.mIsUsedByOtherApps = isUsedByOtherApps;
+                    maybeAddLoadingPackage(owningPackageName, loadingPackageName,
+                            packageUseInfo.mLoadingPackages);
                 } else {
                     // For secondary dex files record the loaderISA and the owner. We'll need
                     // to know under which user to compile and for what ISA.
-                    packageUseInfo.mDexUseInfoMap.put(
-                            dexPath, new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa));
+                    DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa);
+                    packageUseInfo.mDexUseInfoMap.put(dexPath, newData);
+                    maybeAddLoadingPackage(owningPackageName, loadingPackageName,
+                            newData.mLoadingPackages);
                 }
                 mPackageUseInfoMap.put(owningPackageName, packageUseInfo);
                 return true;
@@ -113,10 +129,15 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                 if (primaryOrSplit) {
                     // We have a possible update on the primary apk usage. Merge
                     // isUsedByOtherApps information and return if there was an update.
-                    return packageUseInfo.merge(isUsedByOtherApps);
+                    boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName,
+                            loadingPackageName, packageUseInfo.mLoadingPackages);
+                    return packageUseInfo.merge(isUsedByOtherApps) || updateLoadingPackages;
                 } else {
                     DexUseInfo newData = new DexUseInfo(
                             isUsedByOtherApps, ownerUserId, loaderIsa);
+                    boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName,
+                            loadingPackageName, newData.mLoadingPackages);
+
                     DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath);
                     if (existingData == null) {
                         // It's the first time we see this dex file.
@@ -138,7 +159,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                         }
                         // Merge the information into the existing data.
                         // Returns true if there was an update.
-                        return existingData.merge(newData);
+                        return existingData.merge(newData) || updateLoadingPackages;
                     }
                 }
             }
@@ -185,15 +206,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
      *
      * file_magic_version
      * package_name_1
+     * @ loading_package_1_1, loading_package_1_2...
      * #dex_file_path_1_1
+     * @ loading_package_1_1_1, loading_package_1_1_2...
      * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2
      * #dex_file_path_1_2
+     * @ loading_package_1_2_1, loading_package_1_2_2...
      * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2
      * ...
      * package_name_2
+     * @ loading_package_2_1, loading_package_2_1_2...
      * #dex_file_path_2_1
+     * @ loading_package_2_1_1, loading_package_2_1_2...
      * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2
      * #dex_file_path_2_2,
+     * @ loading_package_2_2_1, loading_package_2_2_2...
      * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2
      * ...
     */
@@ -214,12 +241,17 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
 
             fpw.println(String.join(SPLIT_CHAR, packageName,
                     writeBoolean(packageUseInfo.mIsUsedByOtherApps)));
+            fpw.println(LOADING_PACKAGE_CHAR +
+                    String.join(SPLIT_CHAR, packageUseInfo.mLoadingPackages));
 
             // Write dex file lines.
             for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) {
                 String dexPath = dEntry.getKey();
                 DexUseInfo dexUseInfo = dEntry.getValue();
                 fpw.println(DEX_LINE_CHAR + dexPath);
+                    fpw.println(LOADING_PACKAGE_CHAR +
+                            String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages));
+
                 fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId),
                         writeBoolean(dexUseInfo.mIsUsedByOtherApps)));
                 for (String isa : dexUseInfo.mLoaderIsas) {
@@ -252,6 +284,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
         BufferedReader in = new BufferedReader(reader);
         // Read header, do version check.
         String versionLine = in.readLine();
+        int version;
         if (versionLine == null) {
             throw new IllegalStateException("No version line found.");
         } else {
@@ -259,16 +292,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                 // TODO(calin): the caller is responsible to clear the file.
                 throw new IllegalStateException("Invalid version line: " + versionLine);
             }
-            int version = Integer.parseInt(
+            version = Integer.parseInt(
                     versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length()));
-            if (version != PACKAGE_DEX_USAGE_VERSION) {
+            if (!isSupportedVersion(version)) {
                 throw new IllegalStateException("Unexpected version: " + version);
             }
         }
 
-        String s = null;
-        String currentPakage = null;
-        PackageUseInfo currentPakageData = null;
+        String s;
+        String currentPackage = null;
+        PackageUseInfo currentPackageData = null;
 
         Set<String> supportedIsas = new HashSet<>();
         for (String abi : Build.SUPPORTED_ABIS) {
@@ -280,17 +313,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                 // We expect two lines for each dex entry:
                 // #dexPaths
                 // onwerUserId,isUsedByOtherApps,isa1,isa2
-                if (currentPakage == null) {
+                if (currentPackage == null) {
                     throw new IllegalStateException(
                         "Malformed PackageDexUsage file. Expected package line before dex line.");
                 }
 
                 // First line is the dex path.
                 String dexPath = s.substring(DEX_LINE_CHAR.length());
+
+                // In version 2 the second line contains the list of packages that loaded the file.
+                List<String> loadingPackages = maybeReadLoadingPackages(in, version);
+
                 // Next line is the dex data.
                 s = in.readLine();
                 if (s == null) {
-                    throw new IllegalStateException("Could not fine dexUseInfo for line: " + s);
+                    throw new IllegalStateException("Could not find dexUseInfo for line: " + s);
                 }
 
                 // We expect at least 3 elements (isUsedByOtherApps, userId, isa).
@@ -301,6 +338,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                 int ownerUserId = Integer.parseInt(elems[0]);
                 boolean isUsedByOtherApps = readBoolean(elems[1]);
                 DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId);
+                dexUseInfo.mLoadingPackages.addAll(loadingPackages);
+
                 for (int i = 2; i < elems.length; i++) {
                     String isa = elems[i];
                     if (supportedIsas.contains(isa)) {
@@ -317,7 +356,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                             "unsupported isas. dexPath=" + dexPath);
                     continue;
                 }
-                currentPakageData.mDexUseInfoMap.put(dexPath, dexUseInfo);
+                currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo);
             } else {
                 // This is a package line.
                 // We expect it to be: `packageName,isUsedByOtherApps`.
@@ -325,10 +364,11 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                 if (elems.length != 2) {
                     throw new IllegalStateException("Invalid PackageDexUsage line: " + s);
                 }
-                currentPakage = elems[0];
-                currentPakageData = new PackageUseInfo();
-                currentPakageData.mIsUsedByOtherApps = readBoolean(elems[1]);
-                data.put(currentPakage, currentPakageData);
+                currentPackage = elems[0];
+                currentPackageData = new PackageUseInfo();
+                currentPackageData.mIsUsedByOtherApps = readBoolean(elems[1]);
+                currentPackageData.mLoadingPackages.addAll(maybeReadLoadingPackages(in, version));
+                data.put(currentPackage, currentPackageData);
             }
         }
 
@@ -339,6 +379,43 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
     }
 
     /**
+     * Reads the list of loading packages from the buffer {@parm in} if
+     * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}.
+     */
+    private List<String> maybeReadLoadingPackages(BufferedReader in, int version)
+            throws IOException {
+        if (version == PACKAGE_DEX_USAGE_VERSION) {
+            String line = in.readLine();
+            if (line == null) {
+                throw new IllegalStateException("Could not find the loadingPackages line.");
+            }
+            // We expect that most of the times the list of loading packages will be empty.
+            if (line.length() == LOADING_PACKAGE_CHAR.length()) {
+                return Collections.emptyList();
+            } else {
+                return Arrays.asList(
+                        line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR));
+            }
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's
+     * not equal to {@param owningPackage}
+     */
+    private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage,
+            Set<String> loadingPackages) {
+        return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage);
+    }
+
+    private boolean isSupportedVersion(int version) {
+        return version == PACKAGE_DEX_USAGE_VERSION ||
+                version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1;
+    }
+
+    /**
      * Syncs the existing data with the set of available packages by removing obsolete entries.
      */
     public void syncData(Map<String, Set<Integer>> packageToUsersMap) {
@@ -539,10 +616,13 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
         private boolean mIsUsedByOtherApps;
         // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa).
         private final Map<String, DexUseInfo> mDexUseInfoMap;
+        // Packages who load this dex file.
+        private final Set<String> mLoadingPackages;
 
         public PackageUseInfo() {
             mIsUsedByOtherApps = false;
             mDexUseInfoMap = new HashMap<>();
+            mLoadingPackages = new HashSet<>();
         }
 
         // Creates a deep copy of the `other`.
@@ -552,6 +632,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
             for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) {
                 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue()));
             }
+            mLoadingPackages = new HashSet<>(other.mLoadingPackages);
         }
 
         private boolean merge(boolean isUsedByOtherApps) {
@@ -567,6 +648,10 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
         public Map<String, DexUseInfo> getDexUseInfoMap() {
             return mDexUseInfoMap;
         }
+
+        public Set<String> getLoadingPackages() {
+            return mLoadingPackages;
+        }
     }
 
     /**
@@ -576,6 +661,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
         private boolean mIsUsedByOtherApps;
         private final int mOwnerUserId;
         private final Set<String> mLoaderIsas;
+        // Packages who load this dex file.
+        private final Set<String> mLoadingPackages;
 
         public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) {
             this(isUsedByOtherApps, ownerUserId, null);
@@ -588,6 +675,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
             if (loaderIsa != null) {
                 mLoaderIsas.add(loaderIsa);
             }
+            mLoadingPackages = new HashSet<>();
         }
 
         // Creates a deep copy of the `other`.
@@ -595,13 +683,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
             mIsUsedByOtherApps = other.mIsUsedByOtherApps;
             mOwnerUserId = other.mOwnerUserId;
             mLoaderIsas = new HashSet<>(other.mLoaderIsas);
+            mLoadingPackages = new HashSet<>(other.mLoadingPackages);
         }
 
         private boolean merge(DexUseInfo dexUseInfo) {
             boolean oldIsUsedByOtherApps = mIsUsedByOtherApps;
             mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps;
             boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas);
-            return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps);
+            boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
+            return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps) ||
+                    updateLoadingPackages;
         }
 
         public boolean isUsedByOtherApps() {
@@ -615,5 +706,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
         public Set<String> getLoaderIsas() {
             return mLoaderIsas;
         }
+
+        public Set<String> getLoadingPackages() {
+            return mLoadingPackages;
+        }
     }
 }
index 2e99433..e1ef41e 100644 (file)
@@ -72,25 +72,25 @@ public class PackageDexUsageTests {
         String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
 
         mFooBaseUser0 = new TestData(fooPackageName,
-                fooCodeDir + "base.apk", 0, isa, false, true);
+                fooCodeDir + "base.apk", 0, isa, false, true, fooPackageName);
 
         mFooSplit1User0 = new TestData(fooPackageName,
-                fooCodeDir + "split-1.apk", 0, isa, false, true);
+                fooCodeDir + "split-1.apk", 0, isa, false, true, fooPackageName);
 
         mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName,
-                fooCodeDir + "split-2.apk", 0, isa, true, true);
+                fooCodeDir + "split-2.apk", 0, isa, true, true, "used.by.other.com");
 
         mFooSecondary1User0 = new TestData(fooPackageName,
-                fooDataDir + "sec-1.dex", 0, isa, false, false);
+                fooDataDir + "sec-1.dex", 0, isa, false, false, fooPackageName);
 
         mFooSecondary1User1 = new TestData(fooPackageName,
-                fooDataDir + "sec-1.dex", 1, isa, false, false);
+                fooDataDir + "sec-1.dex", 1, isa, false, false, fooPackageName);
 
         mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName,
-                fooDataDir + "sec-2.dex", 0, isa, true, false);
+                fooDataDir + "sec-2.dex", 0, isa, true, false, "used.by.other.com");
 
         mInvalidIsa = new TestData(fooPackageName,
-                fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true);
+                fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER");
 
         String barPackageName = "com.google.bar";
         String barCodeDir = "/data/app/com.google.bar/";
@@ -98,11 +98,11 @@ public class PackageDexUsageTests {
         String barDataDir1 = "/data/user/1/com.google.bar/";
 
         mBarBaseUser0 = new TestData(barPackageName,
-                barCodeDir + "base.apk", 0, isa, false, true);
+                barCodeDir + "base.apk", 0, isa, false, true, barPackageName);
         mBarSecondary1User0 = new TestData(barPackageName,
-                barDataDir + "sec-1.dex", 0, isa, false, false);
+                barDataDir + "sec-1.dex", 0, isa, false, false, barPackageName);
         mBarSecondary2User1 = new TestData(barPackageName,
-                barDataDir1 + "sec-2.dex", 1, isa, false, false);
+                barDataDir1 + "sec-2.dex", 1, isa, false, false, barPackageName);
     }
 
     @Test
@@ -319,7 +319,8 @@ public class PackageDexUsageTests {
             mFooSplit2UsedByOtherApps0.mOwnerUserId,
             mFooSplit2UsedByOtherApps0.mLoaderIsa,
             /*mIsUsedByOtherApps*/false,
-            mFooSplit2UsedByOtherApps0.mPrimaryOrSplit);
+            mFooSplit2UsedByOtherApps0.mPrimaryOrSplit,
+            mFooSplit2UsedByOtherApps0.mUsedBy);
         assertPackageDexUsage(noLongerUsedByOtherApps);
     }
 
@@ -332,14 +333,65 @@ public class PackageDexUsageTests {
         assertFalse(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName));
     }
 
+    @Test
+    public void testRecordDexFileUsers() {
+        PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage();
+        Set<String> users = new HashSet<>(Arrays.asList(
+                new String[] {"another.package.1"}));
+        Set<String> usersExtra = new HashSet<>(Arrays.asList(
+                new String[] {"another.package.2", "another.package.3"}));
+
+        assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, users));
+        assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, usersExtra));
+
+        assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users));
+        assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra));
+
+        packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers);
+        // Verify that the users were recorded.
+        Set<String> userAll = new HashSet<>(users);
+        userAll.addAll(usersExtra);
+        assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooBaseUser0,
+                mFooSecondary1User0);
+    }
+
+    @Test
+    public void testRecordDexFileUsersNotTheOwningPackage() {
+        PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage();
+        Set<String> users = new HashSet<>(Arrays.asList(
+                new String[] {mFooBaseUser0.mPackageName,}));
+        Set<String> usersExtra = new HashSet<>(Arrays.asList(
+                new String[] {"another.package.2", "another.package.3"}));
+
+        assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, users));
+        assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, usersExtra));
+
+        assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users));
+        assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra));
+
+        packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers);
+        // Verify that only the non owning packages were recorded.
+        assertPackageDexUsage(packageDexUsageRecordUsers, usersExtra, mFooBaseUser0,
+                mFooSecondary1User0);
+    }
+
     private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
+        assertPackageDexUsage(mPackageDexUsage, null, primary, secondaries);
+    }
+
+    private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users,
+            TestData primary, TestData... secondaries) {
         String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
         boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;
-        PackageUseInfo pInfo = mPackageDexUsage.getPackageUseInfo(packageName);
+        PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName);
 
         // Check package use info
         assertNotNull(pInfo);
         assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps());
+        if (users != null) {
+            assertEquals(pInfo.getLoadingPackages(), users);
+        }
+
         Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap();
         assertEquals(secondaries.length, dexUseInfoMap.size());
 
@@ -351,24 +403,43 @@ public class PackageDexUsageTests {
             assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId());
             assertEquals(1, dInfo.getLoaderIsas().size());
             assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa));
+            if (users != null) {
+                 assertEquals(dInfo.getLoadingPackages(), users);
+            }
         }
     }
 
     private boolean record(TestData testData) {
         return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile,
-                testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps,
-                testData.mPrimaryOrSplit);
+               testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps,
+               testData.mPrimaryOrSplit, testData.mUsedBy);
+    }
+
+    private boolean record(PackageDexUsage packageDexUsage, TestData testData, Set<String> users) {
+        boolean result = true;
+        for (String user : users) {
+            result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile,
+                    testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps,
+                    testData.mPrimaryOrSplit, user);
+        }
+        return result;
     }
 
     private void writeAndReadBack() {
+        mPackageDexUsage = writeAndReadBack(mPackageDexUsage);
+    }
+
+    private PackageDexUsage writeAndReadBack(PackageDexUsage packageDexUsage) {
         try {
             StringWriter writer = new StringWriter();
-            mPackageDexUsage.write(writer);
+            packageDexUsage.write(writer);
 
-            mPackageDexUsage = new PackageDexUsage();
-            mPackageDexUsage.read(new StringReader(writer.toString()));
+            PackageDexUsage newPackageDexUsage = new PackageDexUsage();
+            newPackageDexUsage.read(new StringReader(writer.toString()));
+            return newPackageDexUsage;
         } catch (IOException e) {
             fail("Unexpected IOException: " + e.getMessage());
+            return null;
         }
     }
 
@@ -379,15 +450,17 @@ public class PackageDexUsageTests {
         private final String mLoaderIsa;
         private final boolean mUsedByOtherApps;
         private final boolean mPrimaryOrSplit;
+        private final String mUsedBy;
 
         private TestData(String packageName, String dexFile, int ownerUserId,
-                 String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) {
+                 String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) {
             mPackageName = packageName;
             mDexFile = dexFile;
             mOwnerUserId = ownerUserId;
             mLoaderIsa = loaderIsa;
             mUsedByOtherApps = isUsedByOtherApps;
             mPrimaryOrSplit = primaryOrSplit;
+            mUsedBy = usedBy;
         }
 
     }