diff options
| author | 2016-12-16 16:22:00 +0000 | |
|---|---|---|
| committer | 2016-12-19 17:48:35 +0000 | |
| commit | b8976d8f22fecaa9ed39276d9d8ded17d35b51a6 (patch) | |
| tree | 479f7496a4fe6d56ebb6c7ae668abc4c52b4bfd9 | |
| parent | 0318162abcbd07a0472989df43e00e353fac731b (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
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; + } + } +} |