blob: 85a7b8745451e285cb5b27da476a6668fbce0c14 [file] [log] [blame]
/*
* Copyright (C) 2021 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.art;
import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus;
import static com.android.server.art.testing.TestingUtils.deepEq;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.apphibernation.AppHibernationManager;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.filters.SmallTest;
import com.android.server.art.model.Config;
import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.model.OptimizeParams;
import com.android.server.art.model.OptimizeResult;
import com.android.server.art.testing.StaticMockitoRule;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageUserState;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.Mock;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
@SmallTest
@RunWith(Parameterized.class)
public class ArtManagerLocalTest {
private static final String PKG_NAME = "com.example.foo";
private static final String PKG_NAME_SYS_UI = "com.android.systemui";
private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
@Rule
public StaticMockitoRule mockitoRule =
new StaticMockitoRule(SystemProperties.class, Constants.class);
@Mock private ArtManagerLocal.Injector mInjector;
@Mock private PackageManagerLocal mPackageManagerLocal;
@Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
@Mock private IArtd mArtd;
@Mock private DexOptHelper mDexOptHelper;
@Mock private AppHibernationManager mAppHibernationManager;
@Mock private UserManager mUserManager;
private PackageState mPkgState;
private AndroidPackage mPkg;
private Config mConfig;
// True if the primary dex'es are in a readonly partition.
@Parameter(0) public boolean mIsInReadonlyPartition;
private ArtManagerLocal mArtManagerLocal;
@Parameters(name = "isInReadonlyPartition={0}")
public static Iterable<? extends Object> data() {
return List.of(false, true);
}
@Before
public void setUp() throws Exception {
mConfig = new Config();
// Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
// These are the default test setups. They may or may not be used depending on the code path
// that each test case examines.
lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
lenient().when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
lenient().when(mInjector.getConfig()).thenReturn(mConfig);
lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager);
lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile");
lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile");
lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify");
lenient()
.when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
.thenReturn(3);
// No ISA translation.
lenient()
.when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
.thenReturn("");
lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false);
lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true);
lenient()
.when(mUserManager.getUserHandles(anyBoolean()))
.thenReturn(List.of(UserHandle.of(0), UserHandle.of(1)));
lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
List<PackageState> pkgStates = createPackageStates();
for (PackageState pkgState : pkgStates) {
lenient()
.when(mSnapshot.getPackageState(pkgState.getPackageName()))
.thenReturn(pkgState);
}
lenient()
.doAnswer(invocation -> {
var consumer = invocation.<Consumer<PackageState>>getArgument(0);
for (PackageState pkgState : pkgStates) {
consumer.accept(pkgState);
}
return null;
})
.when(mSnapshot)
.forAllPackageStates(any());
mPkgState = mSnapshot.getPackageState(PKG_NAME);
mPkg = mPkgState.getAndroidPackage();
mArtManagerLocal = new ArtManagerLocal(mInjector);
}
@Test
public void testDeleteOptimizedArtifacts() throws Exception {
when(mArtd.deleteArtifacts(any())).thenReturn(1l);
DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
assertThat(result.getFreedBytes()).isEqualTo(4);
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
&& artifactsPath.isa.equals("arm64")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
&& artifactsPath.isa.equals("arm")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
&& artifactsPath.isa.equals("arm64")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
&& artifactsPath.isa.equals("arm")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verifyNoMoreInteractions(mArtd);
}
@Test
public void testDeleteOptimizedArtifactsTranslatedIsas() throws Exception {
lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64");
lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
when(mArtd.deleteArtifacts(any())).thenReturn(1l);
DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
assertThat(result.getFreedBytes()).isEqualTo(4);
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
&& artifactsPath.isa.equals("x86_64")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
&& artifactsPath.isa.equals("x86")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
&& artifactsPath.isa.equals("x86_64")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verify(mArtd).deleteArtifacts(argThat(artifactsPath
-> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
&& artifactsPath.isa.equals("x86")
&& artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
verifyNoMoreInteractions(mArtd);
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteOptimizedArtifactsPackageNotFound() throws Exception {
when(mSnapshot.getPackageState(anyString())).thenReturn(null);
mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteOptimizedArtifactsNoPackage() throws Exception {
when(mPkgState.getAndroidPackage()).thenReturn(null);
mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
}
@Test
public void testGetOptimizationStatus() throws Exception {
when(mArtd.getOptimizationStatus(any(), any(), any()))
.thenReturn(createGetOptimizationStatusResult(
"speed", "compilation-reason-0", "location-debug-string-0"),
createGetOptimizationStatusResult(
"speed-profile", "compilation-reason-1", "location-debug-string-1"),
createGetOptimizationStatusResult(
"verify", "compilation-reason-2", "location-debug-string-2"),
createGetOptimizationStatusResult(
"extract", "compilation-reason-3", "location-debug-string-3"));
OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
List<DexContainerFileOptimizationStatus> statuses =
result.getDexContainerFileOptimizationStatuses();
assertThat(statuses.size()).isEqualTo(4);
assertThat(statuses.get(0).getDexContainerFile()).isEqualTo("/data/app/foo/base.apk");
assertThat(statuses.get(0).isPrimaryAbi()).isEqualTo(true);
assertThat(statuses.get(0).getAbi()).isEqualTo("arm64-v8a");
assertThat(statuses.get(0).getCompilerFilter()).isEqualTo("speed");
assertThat(statuses.get(0).getCompilationReason()).isEqualTo("compilation-reason-0");
assertThat(statuses.get(0).getLocationDebugString()).isEqualTo("location-debug-string-0");
assertThat(statuses.get(1).getDexContainerFile()).isEqualTo("/data/app/foo/base.apk");
assertThat(statuses.get(1).isPrimaryAbi()).isEqualTo(false);
assertThat(statuses.get(1).getAbi()).isEqualTo("armeabi-v7a");
assertThat(statuses.get(1).getCompilerFilter()).isEqualTo("speed-profile");
assertThat(statuses.get(1).getCompilationReason()).isEqualTo("compilation-reason-1");
assertThat(statuses.get(1).getLocationDebugString()).isEqualTo("location-debug-string-1");
assertThat(statuses.get(2).getDexContainerFile()).isEqualTo("/data/app/foo/split_0.apk");
assertThat(statuses.get(2).isPrimaryAbi()).isEqualTo(true);
assertThat(statuses.get(2).getAbi()).isEqualTo("arm64-v8a");
assertThat(statuses.get(2).getCompilerFilter()).isEqualTo("verify");
assertThat(statuses.get(2).getCompilationReason()).isEqualTo("compilation-reason-2");
assertThat(statuses.get(2).getLocationDebugString()).isEqualTo("location-debug-string-2");
assertThat(statuses.get(3).getDexContainerFile()).isEqualTo("/data/app/foo/split_0.apk");
assertThat(statuses.get(3).isPrimaryAbi()).isEqualTo(false);
assertThat(statuses.get(3).getAbi()).isEqualTo("armeabi-v7a");
assertThat(statuses.get(3).getCompilerFilter()).isEqualTo("extract");
assertThat(statuses.get(3).getCompilationReason()).isEqualTo("compilation-reason-3");
assertThat(statuses.get(3).getLocationDebugString()).isEqualTo("location-debug-string-3");
}
@Test(expected = IllegalArgumentException.class)
public void testGetOptimizationStatusPackageNotFound() throws Exception {
when(mSnapshot.getPackageState(anyString())).thenReturn(null);
mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
}
@Test(expected = IllegalArgumentException.class)
public void testGetOptimizationStatusNoPackage() throws Exception {
when(mPkgState.getAndroidPackage()).thenReturn(null);
mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
}
@Test
public void testGetOptimizationStatusNonFatalError() throws Exception {
when(mArtd.getOptimizationStatus(any(), any(), any()))
.thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message"));
OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
List<DexContainerFileOptimizationStatus> statuses =
result.getDexContainerFileOptimizationStatuses();
assertThat(statuses.size()).isEqualTo(4);
for (DexContainerFileOptimizationStatus status : statuses) {
assertThat(status.getCompilerFilter()).isEqualTo("error");
assertThat(status.getCompilationReason()).isEqualTo("error");
assertThat(status.getLocationDebugString()).isEqualTo("some error message");
}
}
@Test
public void testOptimizePackage() throws Exception {
var params = new OptimizeParams.Builder("install").build();
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
same(cancellationSignal), any()))
.thenReturn(result);
assertThat(
mArtManagerLocal.optimizePackage(mSnapshot, PKG_NAME, params, cancellationSignal))
.isSameInstanceAs(result);
}
@Test
public void testOptimizePackages() throws Exception {
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
// It should use the default package list and params.
when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME, PKG_NAME_SYS_UI)), any(),
same(cancellationSignal), any()))
.thenReturn(result);
assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
.isSameInstanceAs(result);
}
@Test
public void testOptimizePackagesOverride() throws Exception {
var params = new OptimizeParams.Builder("bg-dexopt").build();
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
(snapshot, reason, defaultPackages, builder) -> {
assertThat(reason).isEqualTo("bg-dexopt");
assertThat(defaultPackages).containsExactly(PKG_NAME, PKG_NAME_SYS_UI);
builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
});
// It should use the overridden package list and params.
when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
same(cancellationSignal), any()))
.thenReturn(result);
assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
.isSameInstanceAs(result);
}
@Test
public void testOptimizePackagesOverrideCleared() throws Exception {
var params = new OptimizeParams.Builder("bg-dexopt").build();
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
(snapshot, reason, defaultPackages, builder) -> {
builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
});
mArtManagerLocal.clearOptimizePackagesCallback();
// It should use the default package list and params.
when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME, PKG_NAME_SYS_UI)),
not(same(params)), same(cancellationSignal), any()))
.thenReturn(result);
assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
.isSameInstanceAs(result);
}
@Test(expected = IllegalStateException.class)
public void testOptimizePackagesOverrideReasonChanged() throws Exception {
var params = new OptimizeParams.Builder("first-boot").build();
var cancellationSignal = new CancellationSignal();
mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
(snapshot, reason, defaultPackages, builder) -> {
builder.setOptimizeParams(params);
});
mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal);
}
@Test
public void testSnapshotAppProfile() throws Exception {
var options = new MergeProfileOptions();
options.forceMerge = true;
options.forBootImage = false;
File tempFile = File.createTempFile("primary", ".prof");
tempFile.deleteOnExit();
when(mArtd.mergeProfiles(
deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, PKG_NAME, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, PKG_NAME, "primary"))),
isNull(),
deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "primary",
Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
deepEq(List.of("/data/app/foo/base.apk")), deepEq(options)))
.thenAnswer(invocation -> {
try (var writer = new FileWriter(tempFile)) {
writer.write("snapshot");
} catch (IOException e) {
throw new RuntimeException(e);
}
var output = invocation.<OutputProfile>getArgument(2);
output.profilePath.tmpPath = tempFile.getPath();
return true;
});
ParcelFileDescriptor fd =
mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
verify(mArtd).deleteProfile(
argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath())));
try (InputStream inputStream = new AutoCloseInputStream(fd)) {
String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
assertThat(contents).isEqualTo("snapshot");
}
}
@Test
public void testSnapshotAppProfileSplit() throws Exception {
when(mArtd.mergeProfiles(deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef(
PKG_NAME, "split_0.split"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, PKG_NAME, "split_0.split"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, PKG_NAME, "split_0.split"))),
isNull(),
deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "split_0.split",
Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
deepEq(List.of("/data/app/foo/split_0.apk")), any()))
.thenReturn(false);
mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, "split_0");
}
@Test
public void testSnapshotAppProfileEmpty() throws Exception {
when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
ParcelFileDescriptor fd =
mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
verify(mArtd, never()).deleteProfile(any());
try (InputStream inputStream = new AutoCloseInputStream(fd)) {
assertThat(inputStream.readAllBytes()).isEmpty();
}
}
@Test(expected = IllegalArgumentException.class)
public void testSnapshotAppProfilePackageNotFound() throws Exception {
when(mSnapshot.getPackageState(anyString())).thenReturn(null);
mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
}
@Test(expected = IllegalArgumentException.class)
public void testSnapshotAppProfileNoPackage() throws Exception {
when(mPkgState.getAndroidPackage()).thenReturn(null);
mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
}
@Test(expected = IllegalArgumentException.class)
public void testSnapshotAppProfileSplitNotFound() throws Exception {
mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, "non-existent-split");
}
@Test
public void testSnapshotBootImageProfile() throws Exception {
// `lenient()` is required to allow mocking the same method multiple times.
lenient().when(Constants.getenv("BOOTCLASSPATH")).thenReturn("bcp0:bcp1");
lenient().when(Constants.getenv("SYSTEMSERVERCLASSPATH")).thenReturn("sscp0:sscp1");
lenient().when(Constants.getenv("STANDALONE_SYSTEMSERVER_JARS")).thenReturn("sssj0:sssj1");
var options = new MergeProfileOptions();
options.forceMerge = true;
options.forBootImage = true;
when(mArtd.mergeProfiles(
deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef("android", "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, "android", "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, "android", "primary"),
AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, PKG_NAME, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, PKG_NAME, "primary"),
AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, PKG_NAME, "split_0.split"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, PKG_NAME, "split_0.split"),
AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_SYS_UI, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, PKG_NAME_SYS_UI, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, PKG_NAME_SYS_UI, "primary"),
AidlUtils.buildProfilePathForPrimaryRef(
PKG_NAME_HIBERNATING, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
0 /* userId */, PKG_NAME_HIBERNATING, "primary"),
AidlUtils.buildProfilePathForPrimaryCur(
1 /* userId */, PKG_NAME_HIBERNATING, "primary"))),
isNull(),
deepEq(AidlUtils.buildOutputProfileForPrimary("android", "primary",
Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
deepEq(List.of("bcp0", "bcp1", "sscp0", "sscp1", "sssj0", "sssj1")),
deepEq(options)))
.thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
mArtManagerLocal.snapshotBootImageProfile(mSnapshot);
}
private AndroidPackage createPackage(boolean multiSplit) {
AndroidPackage pkg = mock(AndroidPackage.class);
var baseSplit = mock(AndroidPackageSplit.class);
lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
lenient().when(baseSplit.isHasCode()).thenReturn(true);
if (multiSplit) {
// split_0 has code while split_1 doesn't.
var split0 = mock(AndroidPackageSplit.class);
lenient().when(split0.getName()).thenReturn("split_0");
lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
lenient().when(split0.isHasCode()).thenReturn(true);
var split1 = mock(AndroidPackageSplit.class);
lenient().when(split1.getName()).thenReturn("split_1");
lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
lenient().when(split1.isHasCode()).thenReturn(false);
lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1));
} else {
lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
}
return pkg;
}
private PackageUserState createPackageUserState() {
PackageUserState pkgUserState = mock(PackageUserState.class);
lenient().when(pkgUserState.isInstalled()).thenReturn(true);
return pkgUserState;
}
private PackageState createPackageState(
String packageName, int appId, boolean hasPackage, boolean multiSplit) {
PackageState pkgState = mock(PackageState.class);
lenient().when(pkgState.getPackageName()).thenReturn(packageName);
lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
lenient().when(pkgState.isSystem()).thenReturn(mIsInReadonlyPartition);
lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
lenient().when(pkgState.getAppId()).thenReturn(appId);
if (hasPackage) {
AndroidPackage pkg = createPackage(multiSplit);
lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
} else {
lenient().when(pkgState.getAndroidPackage()).thenReturn(null);
}
PackageUserState pkgUserState = createPackageUserState();
lenient().when(pkgState.getStateForUser(any())).thenReturn(pkgUserState);
return pkgState;
}
private List<PackageState> createPackageStates() {
PackageState pkgState = createPackageState(
PKG_NAME, 10001 /* appId */, true /* hasPackage */, true /* multiSplit */);
PackageState sysUiPkgState = createPackageState(
PKG_NAME_SYS_UI, 1234 /* appId */, true /* hasPackage */, false /* multiSplit */);
// This should not be optimized because it's hibernating. However, it should be included
// when snapshotting boot image profile.
PackageState pkgHibernatingState = createPackageState(PKG_NAME_HIBERNATING,
10002 /* appId */, true /* hasPackage */, false /* multiSplit */);
lenient()
.when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING))
.thenReturn(true);
// This should not be optimized because it does't have AndroidPackage.
PackageState nullPkgState = createPackageState("com.example.null", 10003 /* appId */,
false /* hasPackage */, false /* multiSplit */);
// This should not be optimized because it has a negative app id.
PackageState apexPkgState = createPackageState(
"com.android.art", -1 /* appId */, true /* hasPackage */, false /* multiSplit */);
// This should not be optimized because it's "android".
PackageState platformPkgState = createPackageState(Utils.PLATFORM_PACKAGE_NAME,
1000 /* appId */, true /* hasPackage */, false /* multiSplit */);
return List.of(pkgState, sysUiPkgState, pkgHibernatingState, nullPkgState, apexPkgState,
platformPkgState);
}
private GetOptimizationStatusResult createGetOptimizationStatusResult(
String compilerFilter, String compilationReason, String locationDebugString) {
var getOptimizationStatusResult = new GetOptimizationStatusResult();
getOptimizationStatusResult.compilerFilter = compilerFilter;
getOptimizationStatusResult.compilationReason = compilationReason;
getOptimizationStatusResult.locationDebugString = locationDebugString;
return getOptimizationStatusResult;
}
}