summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Calin Juravle <calin@google.com> 2016-12-16 16:22:00 +0000
committer Calin Juravle <calin@google.com> 2016-12-19 17:48:35 +0000
commitb8976d8f22fecaa9ed39276d9d8ded17d35b51a6 (patch)
tree479f7496a4fe6d56ebb6c7ae668abc4c52b4bfd9
parent0318162abcbd07a0472989df43e00e353fac731b (diff)
Record data about dex files use on disk
Add DexManager to keep track of how dex files are used. Every time a dex file is loaded, PackageManager will notify DexManager which will process the load. The DexManager will look up what package owns the dex file and record its use in package-dex-usage.list (through PackageDexUsage). Test: device boots, package-dex-usage.list is created and contains valid data, after device reboot the list is succesfully read from disk. runtest -x .../PackageDexUsageTests.java runtest -x .../DexManagerTests.java Bug: 32871170 Change-Id: If5496c3b95820b537260c326d4eaa04eb50b1d8c
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java28
-rw-r--r--services/core/java/com/android/server/pm/dex/DexManager.java324
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java282
3 files changed, 633 insertions, 1 deletions
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 81978ed2a2ff..4e5127a5f26b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -260,6 +260,7 @@ import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PermissionsState.PermissionState;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
+import com.android.server.pm.dex.DexManager;
import com.android.server.storage.DeviceStorageMonitorInternal;
import dalvik.system.CloseGuard;
@@ -303,6 +304,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -748,6 +750,9 @@ public class PackageManagerService extends IPackageManager.Stub {
final PackageInstallerService mInstallerService;
private final PackageDexOptimizer mPackageDexOptimizer;
+ // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
+ // is used by other apps).
+ private final DexManager mDexManager;
private AtomicInteger mNextMoveId = new AtomicInteger();
private final MoveCallbacks mMoveCallbacks;
@@ -2165,6 +2170,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
+ mDexManager = new DexManager();
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2616,6 +2622,19 @@ public class PackageManagerService extends IPackageManager.Stub {
mPackageUsage.read(mPackages);
mCompilerStats.read();
+ // Read and update the usage of dex files.
+ // At this point we know the code paths of the packages, so we can validate
+ // the disk file and build the internal cache.
+ // The usage file is expected to be small so loading and verifying it
+ // should take a fairly small time compare to the other activities (e.g. package
+ // scanning).
+ final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>();
+ final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+ for (int userId : currentUserIds) {
+ userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList());
+ }
+ mDexManager.load(userPackages);
+
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
@@ -7642,7 +7661,14 @@ public class PackageManagerService extends IPackageManager.Stub {
@Override
public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
- // TODO(calin): b/32871170
+ int userId = UserHandle.getCallingUserId();
+ ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
+ if (ai == null) {
+ Slog.w(TAG, "Loading a package that does not exist for the calling user. package="
+ + loadingPackageName + ", user=" + userId);
+ return;
+ }
+ mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId);
}
// TODO: this is not used nor needed. Delete it.
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
new file mode 100644
index 000000000000..aa2bcefd00e4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser;
+import android.content.pm.ApplicationInfo;
+
+import android.util.Slog;
+
+import com.android.server.pm.PackageManagerServiceUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class keeps track of how dex files are used.
+ * Every time it gets a notification about a dex file being loaded it tracks
+ * its owning package and records it in PackageDexUsage (package-dex-usage.list).
+ *
+ * TODO(calin): Extract related dexopt functionality from PackageManagerService
+ * into this class.
+ */
+public class DexManager {
+ private static final String TAG = "DexManager";
+
+ private static final boolean DEBUG = false;
+
+ // Maps package name to code locations.
+ // It caches the code locations for the installed packages. This allows for
+ // faster lookups (no locks) when finding what package owns the dex file.
+ private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
+
+ // PackageDexUsage handles the actual I/O operations. It is responsible to
+ // encode and save the dex usage data.
+ private final PackageDexUsage mPackageDexUsage;
+
+ // Possible outcomes of a dex search.
+ private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
+ private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk
+ private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk
+ private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex
+
+ public DexManager() {
+ mPackageCodeLocationsCache = new HashMap<>();
+ mPackageDexUsage = new PackageDexUsage();
+ }
+
+ /**
+ * Notify about dex files loads.
+ * Note that this method is invoked when apps load dex files and it should
+ * return as fast as possible.
+ *
+ * @param loadingPackage the package performing the load
+ * @param dexPaths the list of dex files being loaded
+ * @param loaderIsa the ISA of the app loading the dex files
+ * @param loaderUserId the user id which runs the code loading the dex files
+ */
+ public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths,
+ String loaderIsa, int loaderUserId) {
+ try {
+ notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception while notifying dex load for package " +
+ loadingAppInfo.packageName, e);
+ }
+ }
+
+ private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths,
+ String loaderIsa, int loaderUserId) {
+ if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
+ Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " +
+ loaderIsa + "?");
+ return;
+ }
+
+ for (String dexPath : dexPaths) {
+ // Find the owning package name.
+ DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
+
+ if (DEBUG) {
+ Slog.i(TAG, loadingAppInfo.packageName
+ + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
+ }
+
+ if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
+ // TODO(calin): extend isUsedByOtherApps check to detect the cases where
+ // different apps share the same runtime. In that case we should not mark the dex
+ // file as isUsedByOtherApps. Currently this is a safe approximation.
+ boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
+ searchResult.mOwningPackageName);
+ boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
+ searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
+
+ if (primaryOrSplit && !isUsedByOtherApps) {
+ // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
+ // do not record it. This case does not bring any new usable information
+ // and can be safely skipped.
+ continue;
+ }
+
+ // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
+ // 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)) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+ } else {
+ // This can happen in a few situations:
+ // - bogus dex loads
+ // - recent installs/uninstalls that we didn't detect.
+ // - new installed splits
+ // If we can't find the owner of the dex we simply do not track it. The impact is
+ // that the dex file will not be considered for offline optimizations.
+ // TODO(calin): add hooks for install/uninstall notifications to
+ // capture new or obsolete packages.
+ if (DEBUG) {
+ Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
+ }
+ }
+ }
+ }
+
+ /**
+ * Read the dex usage from disk and populate the code cache locations.
+ * @param existingPackages a map containing information about what packages
+ * are available to what users. Only packages in this list will be
+ * recognized during notifyDexLoad().
+ */
+ public void load(Map<Integer, List<PackageInfo>> existingPackages) {
+ try {
+ loadInternal(existingPackages);
+ } catch (Exception e) {
+ mPackageDexUsage.clear();
+ Slog.w(TAG, "Exception while loading package dex usage. " +
+ "Starting with a fresh state.", e);
+ }
+ }
+
+ private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
+ Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
+ // Cache the code locations for the installed packages. This allows for
+ // faster lookups (no locks) when finding what package owns the dex file.
+ for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
+ List<PackageInfo> packageInfoList = entry.getValue();
+ int userId = entry.getKey();
+ for (PackageInfo pi : packageInfoList) {
+ // Cache the code locations.
+ PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
+ if (pcl != null) {
+ pcl.mergeAppDataDirs(pi.applicationInfo, userId);
+ } else {
+ mPackageCodeLocationsCache.put(pi.packageName,
+ new PackageCodeLocations(pi.applicationInfo, userId));
+ }
+ // Cache a map from package name to the set of user ids who installed the package.
+ // We will use it to sync the data and remove obsolete entries from
+ // mPackageDexUsage.
+ Set<Integer> users = putIfAbsent(
+ packageToUsersMap, pi.packageName, new HashSet<>());
+ users.add(userId);
+ }
+ }
+
+ mPackageDexUsage.read();
+ mPackageDexUsage.syncData(packageToUsersMap);
+ }
+
+ /**
+ * Get the package dex usage for the given package name.
+ * @return the package data or null if there is no data available for this package.
+ */
+ public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
+ return mPackageDexUsage.getPackageUseInfo(packageName);
+ }
+
+ /**
+ * Retrieves the package which owns the given dexPath.
+ */
+ private DexSearchResult getDexPackage(
+ ApplicationInfo loadingAppInfo, String dexPath, int userId) {
+ // Ignore framework code.
+ // TODO(calin): is there a better way to detect it?
+ if (dexPath.startsWith("/system/framework/")) {
+ new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
+ }
+
+ // First, check if the package which loads the dex file actually owns it.
+ // Most of the time this will be true and we can return early.
+ PackageCodeLocations loadingPackageCodeLocations =
+ new PackageCodeLocations(loadingAppInfo, userId);
+ int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
+ if (outcome != DEX_SEARCH_NOT_FOUND) {
+ // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
+ return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
+ }
+
+ // The loadingPackage does not own the dex file.
+ // Perform a reverse look-up in the cache to detect if any package has ownership.
+ // Note that we can have false negatives if the cache falls out of date.
+ for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
+ outcome = pcl.searchDex(dexPath, userId);
+ if (outcome != DEX_SEARCH_NOT_FOUND) {
+ return new DexSearchResult(pcl.mPackageName, outcome);
+ }
+ }
+
+ // Cache miss. Return not found for the moment.
+ //
+ // TODO(calin): this may be because of a newly installed package, an update
+ // or a new added user. We can either perform a full look up again or register
+ // observers to be notified of package/user updates.
+ return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
+ }
+
+ private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
+ V existingValue = map.putIfAbsent(key, newValue);
+ return existingValue == null ? newValue : existingValue;
+ }
+
+ /**
+ * Convenience class to store the different locations where a package might
+ * own code.
+ */
+ private static class PackageCodeLocations {
+ private final String mPackageName;
+ private final String mBaseCodePath;
+ private final Set<String> mSplitCodePaths;
+ // Maps user id to the application private directory.
+ private final Map<Integer, Set<String>> mAppDataDirs;
+
+ public PackageCodeLocations(ApplicationInfo ai, int userId) {
+ mPackageName = ai.packageName;
+ mBaseCodePath = ai.sourceDir;
+ mSplitCodePaths = new HashSet<>();
+ if (ai.splitSourceDirs != null) {
+ for (String split : ai.splitSourceDirs) {
+ mSplitCodePaths.add(split);
+ }
+ }
+ mAppDataDirs = new HashMap<>();
+ mergeAppDataDirs(ai, userId);
+ }
+
+ public void mergeAppDataDirs(ApplicationInfo ai, int userId) {
+ Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
+ dataDirs.add(ai.dataDir);
+
+ // Compute and cache the real path as well since data dir may be a symlink.
+ // e.g. /data/data/ -> /data/user/0/
+ try {
+ dataDirs.add(PackageManagerServiceUtils.realpath(new File(ai.dataDir)));
+ } catch (IOException e) {
+ Slog.w(TAG, "Error to get realpath of " + ai.dataDir, e);
+ }
+
+ }
+
+ public int searchDex(String dexPath, int userId) {
+ // First check that this package is installed or active for the given user.
+ // If we don't have a data dir it means this user is trying to load something
+ // unavailable for them.
+ Set<String> userDataDirs = mAppDataDirs.get(userId);
+ if (userDataDirs == null) {
+ Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
+ "user. dexPath=" + dexPath + ", userId=" + userId);
+ return DEX_SEARCH_NOT_FOUND;
+ }
+
+ if (mBaseCodePath.equals(dexPath)) {
+ return DEX_SEARCH_FOUND_PRIMARY;
+ }
+ if (mSplitCodePaths.contains(dexPath)) {
+ return DEX_SEARCH_FOUND_SPLIT;
+ }
+ for (String dataDir : userDataDirs) {
+ if (dexPath.startsWith(dataDir)) {
+ return DEX_SEARCH_FOUND_SECONDARY;
+ }
+ }
+ return DEX_SEARCH_NOT_FOUND;
+ }
+ }
+
+ /**
+ * Convenience class to store ownership search results.
+ */
+ private class DexSearchResult {
+ private String mOwningPackageName;
+ private int mOutcome;
+
+ public DexSearchResult(String owningPackageName, int outcome) {
+ this.mOwningPackageName = owningPackageName;
+ this.mOutcome = outcome;
+ }
+
+ @Override
+ public String toString() {
+ return mOwningPackageName + "-" + mOutcome;
+ }
+ }
+
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
new file mode 100644
index 000000000000..b655f3af3a24
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import android.os.Build;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import dalvik.system.VMRuntime;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DexManagerTests {
+ private DexManager mDexManager;
+
+ private TestData mFooUser0;
+ private TestData mBarUser0;
+ private TestData mBarUser1;
+ private TestData mInvalidIsa;
+ private TestData mDoesNotExist;
+
+ private int mUser0;
+ private int mUser1;
+ @Before
+ public void setup() {
+
+ mUser0 = 0;
+ mUser1 = 1;
+
+ String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
+ String foo = "foo";
+ String bar = "bar";
+
+ mFooUser0 = new TestData(foo, isa, mUser0);
+ mBarUser0 = new TestData(bar, isa, mUser0);
+ mBarUser1 = new TestData(bar, isa, mUser1);
+ mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0);
+ mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1);
+
+
+ mDexManager = new DexManager();
+
+ // Foo and Bar are available to user0.
+ // Only Bar is available to user1;
+ Map<Integer, List<PackageInfo>> existingPackages = new HashMap<>();
+ existingPackages.put(mUser0, Arrays.asList(mFooUser0.mPackageInfo, mBarUser0.mPackageInfo));
+ existingPackages.put(mUser1, Arrays.asList(mBarUser1.mPackageInfo));
+ mDexManager.load(existingPackages);
+ }
+
+ @Test
+ public void testNotifyPrimaryUse() {
+ // The main dex file and splits are re-loaded by the app.
+ notifyDexLoad(mFooUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0);
+
+ // Package is not used by others, so we should get nothing back.
+ assertNull(getPackageUseInfo(mFooUser0));
+ }
+
+ @Test
+ public void testNotifyPrimaryForeignUse() {
+ // Foo loads Bar main apks.
+ notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0);
+
+ // Bar is used by others now and should be in our records
+ PackageUseInfo pui = getPackageUseInfo(mBarUser0);
+ assertNotNull(pui);
+ assertTrue(pui.isUsedByOtherApps());
+ assertTrue(pui.getDexUseInfoMap().isEmpty());
+ }
+
+ @Test
+ public void testNotifySecondary() {
+ // Foo loads its own secondary files.
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+ notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+ }
+
+ @Test
+ public void testNotifySecondaryForeign() {
+ // Foo loads bar secondary files.
+ List<String> barSecondaries = mBarUser0.getSecondaryDexPaths();
+ notifyDexLoad(mFooUser0, barSecondaries, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mBarUser0);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0);
+ }
+
+ @Test
+ public void testNotifySequence() {
+ // Foo loads its own secondary files.
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+ notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+ // Foo loads Bar own secondary files.
+ List<String> barSecondaries = mBarUser0.getSecondaryDexPaths();
+ notifyDexLoad(mFooUser0, barSecondaries, mUser0);
+ // Foo loads Bar primary files.
+ notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0);
+ // Bar loads its own secondary files.
+ notifyDexLoad(mBarUser0, barSecondaries, mUser0);
+ // Bar loads some own secondary files which foo didn't load.
+ List<String> barSecondariesForOwnUse = mBarUser0.getSecondaryDexPathsForOwnUse();
+ notifyDexLoad(mBarUser0, barSecondariesForOwnUse, mUser0);
+
+ // Check bar usage. Should be used by other app (for primary and barSecondaries).
+ PackageUseInfo pui = getPackageUseInfo(mBarUser0);
+ assertNotNull(pui);
+ assertTrue(pui.isUsedByOtherApps());
+ assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(),
+ pui.getDexUseInfoMap().size());
+
+ assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0);
+ assertSecondaryUse(mFooUser0, pui, barSecondariesForOwnUse,
+ /*isUsedByOtherApps*/false, mUser0);
+
+ // Check foo usage. Should not be used by other app.
+ pui = getPackageUseInfo(mFooUser0);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+ }
+
+ @Test
+ public void testPackageUseInfoNotFound() {
+ // Assert we don't get back data we did not previously record.
+ assertNull(getPackageUseInfo(mFooUser0));
+ }
+
+ @Test
+ public void testInvalidIsa() {
+ // Notifying with an invalid ISA should be ignored.
+ notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0);
+ assertNull(getPackageUseInfo(mInvalidIsa));
+ }
+
+ @Test
+ public void testNotExistingPackate() {
+ // Notifying about the load of a package which was previously not
+ // register in DexManager#load should be ignored.
+ notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0);
+ assertNull(getPackageUseInfo(mDoesNotExist));
+ }
+
+ @Test
+ public void testCrossUserAttempt() {
+ // Bar from User1 tries to load secondary dex files from User0 Bar.
+ // Request should be ignored.
+ notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1);
+ assertNull(getPackageUseInfo(mBarUser1));
+ }
+
+ @Test
+ public void testPackageNotInstalledForUser() {
+ // User1 tries to load Foo which is installed for User0 but not for User1.
+ // Note that the PackageManagerService already filters this out but we
+ // still check that nothing goes unexpected in DexManager.
+ notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1);
+ assertNull(getPackageUseInfo(mBarUser1));
+ }
+
+ private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
+ List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
+ for (String dex : secondaries) {
+ DexUseInfo dui = pui.getDexUseInfoMap().get(dex);
+ assertNotNull(dui);
+ assertEquals(isUsedByOtherApps, dui.isUsedByOtherApps());
+ assertEquals(ownerUserId, dui.getOwnerUserId());
+ assertEquals(1, dui.getLoaderIsas().size());
+ assertTrue(dui.getLoaderIsas().contains(testData.mLoaderIsa));
+ }
+ }
+
+ private void notifyDexLoad(TestData testData, List<String> dexPaths, int loaderUserId) {
+ mDexManager.notifyDexLoad(testData.mPackageInfo.applicationInfo, dexPaths,
+ testData.mLoaderIsa, loaderUserId);
+ }
+
+ private PackageUseInfo getPackageUseInfo(TestData testData) {
+ return mDexManager.getPackageUseInfo(testData.mPackageInfo.packageName);
+ }
+
+ private static PackageInfo getMockPackageInfo(String packageName, int userId) {
+ PackageInfo pi = new PackageInfo();
+ pi.packageName = packageName;
+ pi.applicationInfo = getMockApplicationInfo(packageName, userId);
+ return pi;
+ }
+
+ private static ApplicationInfo getMockApplicationInfo(String packageName, int userId) {
+ ApplicationInfo ai = new ApplicationInfo();
+ String codeDir = "/data/app/" + packageName;
+ ai.setBaseCodePath(codeDir + "/base.dex");
+ ai.setSplitCodePaths(new String[] {codeDir + "/split-1.dex", codeDir + "/split-2.dex"});
+ ai.dataDir = "/data/user/" + userId + "/" + packageName;
+ ai.packageName = packageName;
+ return ai;
+ }
+
+ private static class TestData {
+ private final PackageInfo mPackageInfo;
+ private final String mLoaderIsa;
+
+ private TestData(String packageName, String loaderIsa, int userId) {
+ mPackageInfo = getMockPackageInfo(packageName, userId);
+ mLoaderIsa = loaderIsa;
+ }
+
+ private String getPackageName() {
+ return mPackageInfo.packageName;
+ }
+
+ List<String> getSecondaryDexPaths() {
+ List<String> paths = new ArrayList<>();
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary1.dex");
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary2.dex");
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary3.dex");
+ return paths;
+ }
+
+ List<String> getSecondaryDexPathsForOwnUse() {
+ List<String> paths = new ArrayList<>();
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary4.dex");
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary5.dex");
+ return paths;
+ }
+
+ List<String> getBaseAndSplitDexPaths() {
+ List<String> paths = new ArrayList<>();
+ paths.add(mPackageInfo.applicationInfo.sourceDir);
+ for (String split : mPackageInfo.applicationInfo.splitSourceDirs) {
+ paths.add(split);
+ }
+ return paths;
+ }
+ }
+}