blob: 3450710f60a098a735b723b9f0f88bc5c37ce90f [file] [log] [blame]
/*
* 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 static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.MAX_SECONDARY_FILES_PER_OWNER;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
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 android.os.Build;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import dalvik.system.VMRuntime;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PackageDexUsageTests {
private static final String ISA = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
private PackageDexUsage mPackageDexUsage;
private TestData mFooBaseUser0;
private TestData mFooSplit1User0;
private TestData mFooSplit2UsedByOtherApps0;
private TestData mFooSecondary1User0;
private TestData mFooSecondary1User1;
private TestData mFooSecondary2UsedByOtherApps0;
private TestData mInvalidIsa;
private TestData mBarBaseUser0;
private TestData mBarSecondary1User0;
private TestData mBarSecondary2User1;
@Before
public void setup() {
mPackageDexUsage = new PackageDexUsage();
String fooPackageName = "com.google.foo";
String fooCodeDir = "/data/app/com.google.foo/";
String fooDataDir = "/data/user/0/com.google.foo/";
mFooBaseUser0 = new TestData(fooPackageName,
fooCodeDir + "base.apk", 0, ISA, true, fooPackageName);
mFooSplit1User0 = new TestData(fooPackageName,
fooCodeDir + "split-1.apk", 0, ISA, true, fooPackageName);
mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName,
fooCodeDir + "split-2.apk", 0, ISA, true, "used.by.other.com");
mFooSecondary1User0 = new TestData(fooPackageName,
fooDataDir + "sec-1.dex", 0, ISA, false, fooPackageName);
mFooSecondary1User1 = new TestData(fooPackageName,
fooDataDir + "sec-1.dex", 1, ISA, false, fooPackageName);
mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName,
fooDataDir + "sec-2.dex", 0, ISA, false, "used.by.other.com");
mInvalidIsa = new TestData(fooPackageName,
fooCodeDir + "base.apk", 0, "INVALID_ISA", true, "INALID_USER");
String barPackageName = "com.google.bar";
String barCodeDir = "/data/app/com.google.bar/";
String barDataDir = "/data/user/0/com.google.bar/";
String barDataDir1 = "/data/user/1/com.google.bar/";
mBarBaseUser0 = new TestData(barPackageName,
barCodeDir + "base.apk", 0, ISA, true, barPackageName);
mBarSecondary1User0 = new TestData(barPackageName,
barDataDir + "sec-1.dex", 0, ISA, false, barPackageName);
mBarSecondary2User1 = new TestData(barPackageName,
barDataDir1 + "sec-2.dex", 1, ISA, false, barPackageName);
}
@Test
public void testRecordPrimary() {
// Assert new information.
assertTrue(record(mFooBaseUser0));
assertPackageDexUsage(mFooBaseUser0);
writeAndReadBack();
assertPackageDexUsage(mFooBaseUser0);
}
@Test
public void testRecordSplit() {
// Assert new information.
assertTrue(record(mFooSplit1User0));
assertPackageDexUsage(mFooSplit1User0);
writeAndReadBack();
assertPackageDexUsage(mFooSplit1User0);
}
@Test
public void testRecordSplitPrimarySequence() {
// Assert new information.
assertTrue(record(mFooBaseUser0));
assertTrue(record(mFooSplit1User0));
// Assert no new information if we add again
assertFalse(record(mFooBaseUser0));
assertFalse(record(mFooSplit1User0));
assertPackageDexUsage(mFooBaseUser0);
writeAndReadBack();
assertPackageDexUsage(mFooBaseUser0);
// Write Split2 which is used by other apps.
// Assert new information.
assertTrue(record(mFooSplit2UsedByOtherApps0));
assertPackageDexUsage(mFooSplit2UsedByOtherApps0);
writeAndReadBack();
assertPackageDexUsage(mFooSplit2UsedByOtherApps0);
}
@Test
public void testRecordSecondary() {
assertTrue(record(mFooSecondary1User0));
assertPackageDexUsage(null, mFooSecondary1User0);
writeAndReadBack();
assertPackageDexUsage(null, mFooSecondary1User0);
// Recording again does not add more data.
assertFalse(record(mFooSecondary1User0));
assertPackageDexUsage(null, mFooSecondary1User0);
}
@Test
public void testRecordBaseAndSecondarySequence() {
// Write split.
assertTrue(record(mFooSplit2UsedByOtherApps0));
// Write secondary.
assertTrue(record(mFooSecondary1User0));
// Check.
assertPackageDexUsage(mFooSplit2UsedByOtherApps0, mFooSecondary1User0);
writeAndReadBack();
assertPackageDexUsage(mFooSplit2UsedByOtherApps0, mFooSecondary1User0);
// Write another secondary.
assertTrue(record(mFooSecondary2UsedByOtherApps0));
// Check.
assertPackageDexUsage(
mFooSplit2UsedByOtherApps0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
writeAndReadBack();
assertPackageDexUsage(
mFooSplit2UsedByOtherApps0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
}
@Test
public void testRecordTooManySecondaries() {
int tooManyFiles = MAX_SECONDARY_FILES_PER_OWNER + 1;
List<TestData> expectedSecondaries = new ArrayList<>();
for (int i = 1; i <= tooManyFiles; i++) {
String fooPackageName = "com.google.foo";
TestData testData = new TestData(fooPackageName,
"/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false,
fooPackageName);
if (i < tooManyFiles) {
assertTrue("Adding " + testData.mDexFile, record(testData));
expectedSecondaries.add(testData);
} else {
assertFalse("Adding " + testData.mDexFile, record(testData));
}
assertPackageDexUsage(
mPackageDexUsage,
/* usdeBy=*/ (Set<String>) null,
/* primaryDex= */ null,
expectedSecondaries);
}
}
@Test
public void testMultiplePackages() {
assertTrue(record(mFooBaseUser0));
assertTrue(record(mFooSecondary1User0));
assertTrue(record(mFooSecondary2UsedByOtherApps0));
assertTrue(record(mBarBaseUser0));
assertTrue(record(mBarSecondary1User0));
assertTrue(record(mBarSecondary2User1));
assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1);
writeAndReadBack();
assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1);
}
@Test
public void testPackageNotFound() {
assertNull(mPackageDexUsage.getPackageUseInfo("missing.package"));
}
@Test
public void testAttemptToChangeOwner() {
assertTrue(record(mFooSecondary1User0));
try {
record(mFooSecondary1User1);
fail("Expected exception");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testInvalidIsa() {
try {
record(mInvalidIsa);
fail("Expected exception");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testReadWriteEmtpy() {
// Expect no exceptions when writing/reading without data.
writeAndReadBack();
}
@Test
public void testSyncData() {
// Write some records.
assertTrue(record(mFooBaseUser0));
assertTrue(record(mFooSecondary1User0));
assertTrue(record(mFooSecondary2UsedByOtherApps0));
assertTrue(record(mBarBaseUser0));
assertTrue(record(mBarSecondary1User0));
assertTrue(record(mBarSecondary2User1));
// Verify all is good.
assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1);
writeAndReadBack();
assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1);
// Simulate that only user 1 is available.
Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
packageToUsersMap.put(mBarSecondary2User1.mPackageName,
new HashSet<>(Arrays.asList(mBarSecondary2User1.mOwnerUserId)));
Map<String, Set<String>> packageToCodePaths = new HashMap<>();
packageToCodePaths.put(mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile)));
mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths, new ArrayList<String>());
// Assert that only user 1 files are there.
assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1);
assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
}
@Test
public void testSyncDataKeepPackages() {
PackageDexUsage packageDexUsage = new PackageDexUsage();
// Write the record we want to keep and which won't be keep by default.
Set<String> fooUsers = new HashSet<>(Arrays.asList(
new String[] {mFooBaseUser0.mPackageName}));
assertTrue(record(packageDexUsage, mFooBaseUser0, fooUsers));
// Write a record that would be kept by default.
Set<String> barUsers = new HashSet<>(Arrays.asList(
new String[] {"another.package", mFooBaseUser0.mPackageName}));
assertTrue(record(packageDexUsage, mBarBaseUser0, barUsers));
// Construct the user packages and their code paths (things that will be
// kept by default during sync).
Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
packageToUsersMap.put(mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId)));
Map<String, Set<String>> packageToCodePaths = new HashMap<>();
packageToCodePaths.put(mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile)));
// Sync data.
List<String> keepData = new ArrayList<String>();
keepData.add(mFooBaseUser0.mPackageName);
packageDexUsage.syncData(packageToUsersMap, packageToCodePaths, keepData);
// Assert that both packages are kept
assertPackageDexUsage(packageDexUsage, fooUsers, mFooBaseUser0);
// "another.package" should not be in the loading packages after sync.
Set<String> expectedBarUsers = new HashSet<>(Arrays.asList(
new String[] {mFooBaseUser0.mPackageName}));
assertPackageDexUsage(packageDexUsage, expectedBarUsers,
mBarBaseUser0.updateUsedBy(mFooBaseUser0.mPackageName));
}
@Test
public void testRemovePackage() {
// Record Bar secondaries for two different users.
assertTrue(record(mBarSecondary1User0));
assertTrue(record(mBarSecondary2User1));
// Remove the package.
assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName));
// Assert that we can't find the package anymore.
assertNull(mPackageDexUsage.getPackageUseInfo(mBarSecondary1User0.mPackageName));
}
@Test
public void testRemoveNonexistentPackage() {
// Record Bar secondaries for two different users.
assertTrue(record(mBarSecondary1User0));
// Remove the package.
assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName));
// Remove the package again. It should return false because the package no longer
// has a record in the use info.
assertFalse(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName));
}
@Test
public void testRemoveUserPackage() {
// Record Bar secondaries for two different users.
assertTrue(record(mBarSecondary1User0));
assertTrue(record(mBarSecondary2User1));
// Remove user 0 files.
assertTrue(mPackageDexUsage.removeUserPackage(mBarSecondary1User0.mPackageName,
mBarSecondary1User0.mOwnerUserId));
// Assert that only user 1 files are there.
assertPackageDexUsage(null, mBarSecondary2User1);
}
@Test
public void testRemoveDexFile() {
// Record Bar secondaries for two different users.
assertTrue(record(mBarSecondary1User0));
assertTrue(record(mBarSecondary2User1));
// Remove mBarSecondary1User0 file.
assertTrue(mPackageDexUsage.removeDexFile(mBarSecondary1User0.mPackageName,
mBarSecondary1User0.mDexFile, mBarSecondary1User0.mOwnerUserId));
// Assert that only user 1 files are there.
assertPackageDexUsage(null, mBarSecondary2User1);
}
@Test
public void testClearUsedByOtherApps() {
// Write a package which is used by other apps.
assertTrue(record(mFooSplit2UsedByOtherApps0));
assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName));
// Check that the package is no longer used by other apps.
TestData noLongerUsedByOtherApps = new TestData(
mFooSplit2UsedByOtherApps0.mPackageName,
mFooSplit2UsedByOtherApps0.mDexFile,
mFooSplit2UsedByOtherApps0.mOwnerUserId,
mFooSplit2UsedByOtherApps0.mLoaderIsa,
mFooSplit2UsedByOtherApps0.mPrimaryOrSplit,
/*usedBy=*/ null);
assertPackageDexUsage(noLongerUsedByOtherApps);
}
@Test
public void testClearUsedByOtherAppsNonexistent() {
// Write a package which is used by other apps.
assertTrue(record(mFooSplit2UsedByOtherApps0));
assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName));
// Clearing again should return false as there should be no update on the use info.
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, mFooSplit2UsedByOtherApps0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra));
assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, usersExtra));
packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers);
// Verify that the users were recorded.
Set<String> userAll = new HashSet<>(users);
userAll.addAll(usersExtra);
assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0,
mFooSecondary2UsedByOtherApps0);
}
@Test
public void testRecordDexFileUsersAndTheOwningPackage() {
PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage();
Set<String> users = new HashSet<>(Arrays.asList(
new String[] {mFooSplit2UsedByOtherApps0.mPackageName}));
Set<String> usersExtra = new HashSet<>(Arrays.asList(
new String[] {"another.package.2", "another.package.3"}));
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra));
packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers);
Set<String> expectedUsers = new HashSet<>(users);
expectedUsers.addAll(usersExtra);
// Verify that all loading packages were recorded.
assertPackageDexUsage(
packageDexUsageRecordUsers, expectedUsers, mFooSplit2UsedByOtherApps0);
}
@Test
public void testRecordClassLoaderContextVariableContext() {
// Record a secondary dex file.
assertTrue(record(mFooSecondary1User0));
// Now update its context.
TestData fooSecondary1User0NewContext = mFooSecondary1User0.updateClassLoaderContext(
"PCL[new_context.dex]");
assertTrue(record(fooSecondary1User0NewContext));
// Now check that the context was switch to variable.
TestData expectedContext = mFooSecondary1User0.updateClassLoaderContext(
PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT);
assertPackageDexUsage(null, expectedContext);
writeAndReadBack();
assertPackageDexUsage(null, expectedContext);
}
@Test
public void testRecordClassLoaderContextOverwritten() {
// Record a secondary dex file.
assertTrue(record(mFooSecondary1User0));
// Now update its context.
TestData fooSecondary1User0NewContext = mFooSecondary1User0.updateClassLoaderContext(
"PCL[new_context.dex]", true);
assertTrue(record(fooSecondary1User0NewContext));
// Now check that the context was overwritten.
TestData expectedContext = mFooSecondary1User0.updateClassLoaderContext(
"PCL[new_context.dex]", true);
assertPackageDexUsage(null, expectedContext);
}
@Test
public void testDexUsageClassLoaderContext() {
final boolean isUsedByOtherApps = false;
final int userId = 0;
PackageDexUsage.DexUseInfo validContext = new DexUseInfo(isUsedByOtherApps, userId,
"valid_context", "arm");
assertFalse(validContext.isUnsupportedClassLoaderContext());
assertFalse(validContext.isVariableClassLoaderContext());
PackageDexUsage.DexUseInfo variableContext = new DexUseInfo(isUsedByOtherApps, userId,
PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, "arm");
assertFalse(variableContext.isUnsupportedClassLoaderContext());
assertTrue(variableContext.isVariableClassLoaderContext());
}
@Test
public void testRead() {
String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
// Equivalent to
// record(mFooSplit2UsedByOtherApps0);
// record(mFooSecondary1User0);
// record(mFooSecondary2UsedByOtherApps0);
// record(mBarBaseUser0);
// record(mBarSecondary1User0);
String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n"
+ "com.google.foo\n"
+ "+/data/app/com.google.foo/split-2.apk\n"
+ "@used.by.other.com\n"
+ "#/data/user/0/com.google.foo/sec-2.dex\n"
+ "0,1," + ISA + "\n"
+ "@used.by.other.com\n"
+ "PCL[/data/user/0/com.google.foo/sec-2.dex]\n"
+ "#/data/user/0/com.google.foo/sec-1.dex\n"
+ "0,0," + ISA + "\n"
+ "@\n"
+ "PCL[/data/user/0/com.google.foo/sec-1.dex]\n"
+ "com.google.bar\n"
+ "+/data/app/com.google.bar/base.apk\n"
+ "@com.google.bar\n"
+ "#/data/user/0/com.google.bar/sec-1.dex\n"
+ "0,0," + ISA + "\n"
+ "@\n"
+ "PCL[/data/user/0/com.google.bar/sec-1.dex]";
PackageDexUsage packageDexUsage = new PackageDexUsage();
try {
packageDexUsage.read(new StringReader(content));
} catch (IOException e) {
fail();
}
// After the read we must sync the data to fill the missing information on the code paths.
Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
Map<String, Set<String>> packageToCodePaths = new HashMap<>();
// Handle foo package.
packageToUsersMap.put(
mFooSplit2UsedByOtherApps0.mPackageName,
new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId)));
packageToCodePaths.put(
mFooSplit2UsedByOtherApps0.mPackageName,
new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mDexFile,
mFooSplit1User0.mDexFile, mFooBaseUser0.mDexFile)));
// Handle bar package.
packageToUsersMap.put(
mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId)));
packageToCodePaths.put(
mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile)));
// Handle the loading package.
packageToUsersMap.put(
mFooSplit2UsedByOtherApps0.mUsedBy,
new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId)));
// Sync the data.
packageDexUsage.syncData(packageToUsersMap, packageToCodePaths, new ArrayList<>());
// Assert foo code paths.
assertPackageDexUsage(
packageDexUsage,
/*nonDefaultUsers=*/ null,
mFooSplit2UsedByOtherApps0,
mFooSecondary2UsedByOtherApps0,
mFooSecondary1User0);
// Assert bar code paths.
assertPackageDexUsage(
packageDexUsage,
/*nonDefaultUsers=*/ null,
mBarBaseUser0,
mBarSecondary1User0);
}
@Test
public void testUnsupportedClassLoaderDiscardedOnRead() throws Exception {
String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n"
+ mBarSecondary1User0.mPackageName + "\n"
+ "#" + mBarSecondary1User0.mDexFile + "\n"
+ "0,0," + mBarSecondary1User0.mLoaderIsa + "\n"
+ "@\n"
+ "=UnsupportedClassLoaderContext=\n"
+ mFooSecondary1User0.mPackageName + "\n"
+ "#" + mFooSecondary1User0.mDexFile + "\n"
+ "0,0," + mFooSecondary1User0.mLoaderIsa + "\n"
+ "@\n"
+ mFooSecondary1User0.mClassLoaderContext + "\n";
mPackageDexUsage.read(new StringReader(content));
assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0);
assertPackageDexUsage(mBarBaseUser0);
}
@Test
public void testEnsureLoadingPackagesCanBeExtended() {
String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n"
+ "com.google.foo\n"
+ "+/data/app/com.google.foo/split-2.apk\n"
+ "@\n";
PackageDexUsage packageDexUsage = new PackageDexUsage();
try {
packageDexUsage.read(new StringReader(content));
} catch (IOException e) {
fail();
}
record(packageDexUsage, mFooSplit2UsedByOtherApps0, mFooSplit2UsedByOtherApps0.getUsedBy());
}
private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
assertPackageDexUsage(mPackageDexUsage, null, primary, secondaries);
}
private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users,
TestData primary, TestData... secondaries) {
assertPackageDexUsage(packageDexUsage, users, primary, Arrays.asList(secondaries));
}
private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users,
TestData primary, List<TestData> secondaries) {
String packageName = primary == null
? secondaries.get(0).mPackageName
: primary.mPackageName;
boolean primaryUsedByOtherApps = primary != null && primary.isUsedByOtherApps();
PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName);
// Check package use info
assertNotNull(pInfo);
if (primary != null) {
if (users != null) {
assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users);
} else if (pInfo.getLoadingPackages(primary.mDexFile) != null) {
assertEquals(pInfo.getLoadingPackages(primary.mDexFile), primary.getUsedBy());
}
assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile));
}
Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap();
assertEquals(secondaries.size(), dexUseInfoMap.size());
// Check dex use info
for (TestData testData : secondaries) {
DexUseInfo dInfo = dexUseInfoMap.get(testData.mDexFile);
assertNotNull(dInfo);
if (users != null) {
assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), users);
} else {
assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), testData.getUsedBy());
}
assertEquals(testData.isUsedByOtherApps(), dInfo.isUsedByOtherApps());
assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId());
assertEquals(1, dInfo.getLoaderIsas().size());
assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa));
assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext());
}
}
private boolean record(TestData testData) {
return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile,
testData.mOwnerUserId, testData.mLoaderIsa,
testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext,
testData.mOverwriteCLC);
}
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.mPrimaryOrSplit, user, testData.mClassLoaderContext,
testData.mOverwriteCLC);
}
return result;
}
private void writeAndReadBack() {
mPackageDexUsage = writeAndReadBack(mPackageDexUsage);
}
private PackageDexUsage writeAndReadBack(PackageDexUsage packageDexUsage) {
try {
StringWriter writer = new StringWriter();
packageDexUsage.write(writer);
PackageDexUsage newPackageDexUsage = new PackageDexUsage();
newPackageDexUsage.read(new StringReader(writer.toString()));
return newPackageDexUsage;
} catch (IOException e) {
fail("Unexpected IOException: " + e.getMessage());
return null;
}
}
private static class TestData {
private final String mPackageName;
private final String mDexFile;
private final int mOwnerUserId;
private final String mLoaderIsa;
private final boolean mPrimaryOrSplit;
private final String mUsedBy;
private final String mClassLoaderContext;
private final boolean mOverwriteCLC;
private TestData(String packageName, String dexFile, int ownerUserId,
String loaderIsa, boolean primaryOrSplit, String usedBy) {
this(packageName, dexFile, ownerUserId, loaderIsa, primaryOrSplit,
usedBy, "PCL[" + dexFile + "]", false);
}
private TestData(String packageName, String dexFile, int ownerUserId,
String loaderIsa, boolean primaryOrSplit, String usedBy,
String classLoaderContext, boolean overwriteCLC) {
mPackageName = packageName;
mDexFile = dexFile;
mOwnerUserId = ownerUserId;
mLoaderIsa = loaderIsa;
mPrimaryOrSplit = primaryOrSplit;
mUsedBy = usedBy;
mClassLoaderContext = classLoaderContext;
mOverwriteCLC = overwriteCLC;
}
private TestData updateClassLoaderContext(String newContext) {
return updateClassLoaderContext(newContext, mOverwriteCLC);
}
private TestData updateClassLoaderContext(String newContext, boolean overwriteCLC) {
return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa,
mPrimaryOrSplit, mUsedBy, newContext, overwriteCLC);
}
private TestData updateUsedBy(String newUsedBy) {
return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa,
mPrimaryOrSplit, newUsedBy, mClassLoaderContext, mOverwriteCLC);
}
private boolean isUsedByOtherApps() {
return mUsedBy != null && !mPackageName.equals(mUsedBy);
}
private Set<String> getUsedBy() {
Set<String> users = new HashSet<>();
if ((mUsedBy != null) && (mPrimaryOrSplit || isUsedByOtherApps())) {
// We do not store the loading package for secondary dex files
// which are not used by others.
users.add(mUsedBy);
}
return users;
}
}
}