Record data about dex files use on disk am: 5a9094c1b6
am: dc10a3bdb0
Change-Id: Ife1e626d722ba129cdcb6cd4a8f454cf28ca267c
This commit is contained in:
@@ -253,6 +253,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;
|
||||
@@ -295,6 +296,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;
|
||||
@@ -712,6 +714,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;
|
||||
@@ -2116,6 +2121,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(
|
||||
@@ -2555,6 +2561,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: "
|
||||
@@ -7389,7 +7408,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.
|
||||
|
||||
324
services/core/java/com/android/server/pm/dex/DexManager.java
Normal file
324
services/core/java/com/android/server/pm/dex/DexManager.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user