• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.art;
18 
19 import static android.app.ActivityManager.RunningAppProcessInfo;
20 import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
21 
22 import static com.android.server.art.DexUseManagerLocal.CheckedSecondaryDexInfo;
23 import static com.android.server.art.ProfilePath.PrimaryCurProfilePath;
24 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
25 import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
26 import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
27 import static com.android.server.art.testing.TestingUtils.deepEq;
28 import static com.android.server.art.testing.TestingUtils.inAnyOrder;
29 import static com.android.server.art.testing.TestingUtils.inAnyOrderDeepEquals;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import static org.mockito.AdditionalMatchers.not;
34 import static org.mockito.ArgumentMatchers.anyString;
35 import static org.mockito.ArgumentMatchers.eq;
36 import static org.mockito.ArgumentMatchers.matches;
37 import static org.mockito.Mockito.any;
38 import static org.mockito.Mockito.anyBoolean;
39 import static org.mockito.Mockito.anyInt;
40 import static org.mockito.Mockito.argThat;
41 import static org.mockito.Mockito.doReturn;
42 import static org.mockito.Mockito.inOrder;
43 import static org.mockito.Mockito.isNull;
44 import static org.mockito.Mockito.lenient;
45 import static org.mockito.Mockito.mock;
46 import static org.mockito.Mockito.never;
47 import static org.mockito.Mockito.same;
48 import static org.mockito.Mockito.times;
49 import static org.mockito.Mockito.verify;
50 import static org.mockito.Mockito.when;
51 
52 import android.app.ActivityManager;
53 import android.apphibernation.AppHibernationManager;
54 import android.content.BroadcastReceiver;
55 import android.content.Context;
56 import android.content.Intent;
57 import android.os.CancellationSignal;
58 import android.os.ParcelFileDescriptor;
59 import android.os.Process;
60 import android.os.ServiceSpecificException;
61 import android.os.SystemProperties;
62 import android.os.UserHandle;
63 import android.os.UserManager;
64 import android.os.storage.StorageManager;
65 import android.system.OsConstants;
66 
67 import androidx.test.filters.SmallTest;
68 
69 import com.android.modules.utils.pm.PackageStateModulesUtils;
70 import com.android.server.art.model.ArtFlags;
71 import com.android.server.art.model.ArtManagedFileStats;
72 import com.android.server.art.model.BatchDexoptParams;
73 import com.android.server.art.model.Config;
74 import com.android.server.art.model.DeleteResult;
75 import com.android.server.art.model.DexoptParams;
76 import com.android.server.art.model.DexoptResult;
77 import com.android.server.art.model.DexoptStatus;
78 import com.android.server.art.prereboot.PreRebootStatsReporter;
79 import com.android.server.art.proto.DexMetadataConfig;
80 import com.android.server.art.testing.StaticMockitoRule;
81 import com.android.server.art.testing.TestingUtils;
82 import com.android.server.pm.PackageManagerLocal;
83 import com.android.server.pm.pkg.AndroidPackage;
84 import com.android.server.pm.pkg.AndroidPackageSplit;
85 import com.android.server.pm.pkg.PackageState;
86 import com.android.server.pm.pkg.PackageUserState;
87 
88 import org.junit.Before;
89 import org.junit.Rule;
90 import org.junit.Test;
91 import org.junit.runner.RunWith;
92 import org.junit.runners.Parameterized;
93 import org.junit.runners.Parameterized.Parameter;
94 import org.junit.runners.Parameterized.Parameters;
95 import org.mockito.ArgumentCaptor;
96 import org.mockito.InOrder;
97 import org.mockito.Mock;
98 
99 import java.io.File;
100 import java.io.FileWriter;
101 import java.io.IOException;
102 import java.io.InputStream;
103 import java.nio.charset.StandardCharsets;
104 import java.nio.file.Files;
105 import java.nio.file.NoSuchFileException;
106 import java.nio.file.Path;
107 import java.util.List;
108 import java.util.Map;
109 import java.util.Set;
110 import java.util.concurrent.Executor;
111 import java.util.concurrent.ForkJoinPool;
112 import java.util.concurrent.TimeUnit;
113 import java.util.function.Consumer;
114 import java.util.stream.Collectors;
115 import java.util.zip.ZipFile;
116 
117 @SmallTest
118 @RunWith(Parameterized.class)
119 public class ArtManagerLocalTest {
120     private static final String PKG_NAME_1 = "com.example.foo";
121     private static final String PKG_NAME_2 = "com.android.bar";
122     private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
123     private static final int INACTIVE_DAYS = 1;
124     private static final long CURRENT_TIME_MS = 10000000000l;
125     private static final long RECENT_TIME_MS =
126             CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) + 1;
127     private static final long NOT_RECENT_TIME_MS =
128             CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) - 1;
129     private static final int APP_ID = 1000;
130 
131     @Rule
132     public StaticMockitoRule mockitoRule = new StaticMockitoRule(
133             SystemProperties.class, Constants.class, PackageStateModulesUtils.class);
134 
135     @Mock private ArtManagerLocal.Injector mInjector;
136     @Mock private ArtFileManager.Injector mArtFileManagerInjector;
137     @Mock private PackageManagerLocal mPackageManagerLocal;
138     @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
139     @Mock private IArtd mArtd;
140     @Mock private DexoptHelper mDexoptHelper;
141     @Mock private AppHibernationManager mAppHibernationManager;
142     @Mock private UserManager mUserManager;
143     @Mock private DexUseManagerLocal mDexUseManager;
144     @Mock private StorageManager mStorageManager;
145     @Mock private ArtdRefCache.Pin mArtdPin;
146     @Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector;
147     @Mock private Context mContext;
148     @Mock private PreRebootDexoptJob mPreRebootDexoptJob;
149     @Mock private PreRebootStatsReporter.Injector mPreRebootStatsReporterInjector;
150     @Mock private ActivityManager mActivityManager;
151     private PackageState mPkgState1;
152     private AndroidPackage mPkg1;
153     private CheckedSecondaryDexInfo mPkg1SecondaryDexInfo1;
154     private CheckedSecondaryDexInfo mPkg1SecondaryDexInfoNotFound;
155     private Config mConfig;
156     private DexMetadataHelper mDexMetadataHelper;
157     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
158 
159     // True if the artifacts should be in dalvik-cache.
160     @Parameter(0) public boolean mIsInDalvikCache;
161 
162     private ArtManagerLocal mArtManagerLocal;
163 
164     @Parameters(name = "mIsInDalvikCache={0}")
data()165     public static Iterable<Object[]> data() {
166         return List.of(new Object[] {true}, new Object[] {false});
167     }
168 
169     @Before
setUp()170     public void setUp() throws Exception {
171         mConfig = new Config();
172         mDexMetadataHelper = new DexMetadataHelper(mDexMetadataHelperInjector);
173 
174         // Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
175         // These are the default test setups. They may or may not be used depending on the code path
176         // that each test case examines.
177         lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
178         lenient().when(mInjector.getArtd()).thenReturn(mArtd);
179         lenient().when(mInjector.createArtdPin()).thenReturn(mArtdPin);
180         lenient().when(mInjector.getDexoptHelper()).thenReturn(mDexoptHelper);
181         lenient().when(mInjector.getConfig()).thenReturn(mConfig);
182         lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager);
183         lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
184         lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
185         lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false);
186         lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
187         lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS);
188         lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
189         lenient()
190                 .when(mInjector.getArtFileManager())
191                 .thenReturn(new ArtFileManager(mArtFileManagerInjector));
192         lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper);
193         lenient().when(mInjector.getContext()).thenReturn(mContext);
194         lenient().when(mInjector.getPreRebootDexoptJob()).thenReturn(mPreRebootDexoptJob);
195         lenient()
196                 .when(mInjector.getPreRebootStatsReporter())
197                 .thenAnswer(
198                         invocation -> new PreRebootStatsReporter(mPreRebootStatsReporterInjector));
199         lenient().when(mInjector.getActivityManager()).thenReturn(mActivityManager);
200 
201         lenient().when(mArtFileManagerInjector.getArtd()).thenReturn(mArtd);
202         lenient().when(mArtFileManagerInjector.getUserManager()).thenReturn(mUserManager);
203         lenient().when(mArtFileManagerInjector.getDexUseManager()).thenReturn(mDexUseManager);
204         lenient().when(mArtFileManagerInjector.isSystemOrRootOrShell()).thenReturn(true);
205 
206         Path tempDir = Files.createTempDirectory("temp");
207         tempDir.toFile().deleteOnExit();
208         lenient().when(mInjector.getTempDir()).thenReturn(tempDir.toString());
209 
210         lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile");
211         lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile");
212         lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify");
213         lenient().when(SystemProperties.get(eq("pm.dexopt.boot-after-ota"))).thenReturn("verify");
214         lenient()
215                 .when(SystemProperties.get(eq("pm.dexopt.boot-after-mainline-update")))
216                 .thenReturn("verify");
217         lenient().when(SystemProperties.get(eq("pm.dexopt.inactive"))).thenReturn("verify");
218         lenient()
219                 .when(SystemProperties.getInt(matches("pm\\.dexopt\\..*\\.concurrency"), anyInt()))
220                 .thenReturn(3);
221         lenient()
222                 .when(SystemProperties.getInt(
223                         matches("persist\\.device_config\\.runtime\\..*_concurrency"), anyInt()))
224                 .thenReturn(3);
225         lenient()
226                 .when(SystemProperties.getInt(
227                         eq("pm.dexopt.downgrade_after_inactive_days"), anyInt()))
228                 .thenReturn(INACTIVE_DAYS);
229 
230         // No ISA translation.
231         lenient()
232                 .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
233                 .thenReturn("");
234 
235         lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
236         lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
237         lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
238         lenient().when(Constants.isBootImageProfilingEnabled()).thenReturn(true);
239 
240         lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false);
241         lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true);
242 
243         lenient()
244                 .when(mUserManager.getUserHandles(anyBoolean()))
245                 .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1)));
246 
247         // All packages are by default recently used.
248         lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(RECENT_TIME_MS);
249         mPkg1SecondaryDexInfo1 = createSecondaryDexInfo("/data/user/0/foo/1.apk", UserHandle.of(0));
250         mPkg1SecondaryDexInfoNotFound =
251                 createSecondaryDexInfo("/data/user/0/foo/not_found.apk", UserHandle.of(0));
252         lenient()
253                 .doReturn(List.of(mPkg1SecondaryDexInfo1, mPkg1SecondaryDexInfoNotFound))
254                 .when(mDexUseManager)
255                 .getSecondaryDexInfo(eq(PKG_NAME_1));
256         lenient()
257                 .doReturn(List.of(mPkg1SecondaryDexInfo1, mPkg1SecondaryDexInfoNotFound))
258                 .when(mDexUseManager)
259                 .getCheckedSecondaryDexInfo(
260                         eq(PKG_NAME_1), eq(false) /* excludeObsoleteDexesAndLoaders */);
261         lenient()
262                 .doReturn(List.of(mPkg1SecondaryDexInfo1))
263                 .when(mDexUseManager)
264                 .getCheckedSecondaryDexInfo(
265                         eq(PKG_NAME_1), eq(true) /* excludeObsoleteDexesAndLoaders */);
266 
267         simulateStorageNotLow();
268 
269         lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
270         List<PackageState> pkgStates = createPackageStates();
271         for (PackageState pkgState : pkgStates) {
272             lenient()
273                     .when(mSnapshot.getPackageState(pkgState.getPackageName()))
274                     .thenReturn(pkgState);
275         }
276         var packageStateMap = pkgStates.stream().collect(
277                 Collectors.toMap(PackageState::getPackageName, it -> it));
278         lenient().when(mSnapshot.getPackageStates()).thenReturn(packageStateMap);
279         mPkgState1 = mSnapshot.getPackageState(PKG_NAME_1);
280         mPkg1 = mPkgState1.getAndroidPackage();
281 
282         lenient().when(mArtd.isInDalvikCache(any())).thenReturn(mIsInDalvikCache);
283 
284         // By default, none of the profiles are usable.
285         lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
286         lenient()
287                 .when(mArtd.copyAndRewriteProfile(any(), any(), any()))
288                 .thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
289         lenient()
290                 .when(mArtd.copyAndRewriteEmbeddedProfile(any(), any()))
291                 .thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
292 
293         lenient()
294                 .when(mDexMetadataHelperInjector.openZipFile(any()))
295                 .thenThrow(NoSuchFileException.class);
296 
297         mBroadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
298         lenient()
299                 .when(mContext.registerReceiver(mBroadcastReceiverCaptor.capture(), any()))
300                 .thenReturn(mock(Intent.class));
301 
302         File tempFile = File.createTempFile("pre-reboot-stats", ".pb");
303         tempFile.deleteOnExit();
304         lenient()
305                 .when(mPreRebootStatsReporterInjector.getFilename())
306                 .thenReturn(tempFile.getAbsolutePath());
307 
308         mArtManagerLocal = new ArtManagerLocal(mInjector);
309     }
310 
311     @Test
testDeleteDexoptArtifacts()312     public void testDeleteDexoptArtifacts() throws Exception {
313         final long DEXOPT_ARTIFACTS_FREED = 1l;
314         final long RUNTIME_ARTIFACTS_FREED = 100l;
315 
316         when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED);
317         when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED);
318 
319         DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
320         assertThat(result.getFreedBytes())
321                 .isEqualTo(6 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED);
322 
323         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
324                 "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache)));
325         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
326                 "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache)));
327         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
328                 "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache)));
329         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
330                 "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)));
331         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
332                 "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
333         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
334                 "/data/user/0/foo/not_found.apk", "arm64", false /* isInDalvikCache */)));
335 
336         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
337                 "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache)));
338         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
339                 "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache)));
340         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
341                 "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache)));
342         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
343                 "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)));
344 
345         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
346                 PKG_NAME_1, "/somewhere/app/foo/base.apk", "arm64")));
347         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
348                 PKG_NAME_1, "/somewhere/app/foo/base.apk", "arm")));
349         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
350                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64")));
351         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
352                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")));
353 
354         // Verify that there are no more calls than the ones above.
355         verify(mArtd, times(6)).deleteArtifacts(any());
356         verify(mArtd, times(4)).deleteSdmSdcFiles(any());
357         verify(mArtd, times(4)).deleteRuntimeArtifacts(any());
358     }
359 
360     @Test
testDeleteDexoptArtifactsTranslatedIsas()361     public void testDeleteDexoptArtifactsTranslatedIsas() throws Exception {
362         final long DEXOPT_ARTIFACTS_FREED = 1l;
363         final long RUNTIME_ARTIFACTS_FREED = 100l;
364 
365         lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
366         lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
367         lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
368         lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64");
369         lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
370         when(mPkg1SecondaryDexInfo1.abiNames()).thenReturn(Set.of("x86_64"));
371         when(mPkg1SecondaryDexInfoNotFound.abiNames()).thenReturn(Set.of("x86_64"));
372 
373         when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED);
374         when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED);
375 
376         DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
377         assertThat(result.getFreedBytes())
378                 .isEqualTo(6 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED);
379 
380         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
381                 "/somewhere/app/foo/base.apk", "x86_64", mIsInDalvikCache)));
382         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
383                 "/somewhere/app/foo/base.apk", "x86", mIsInDalvikCache)));
384         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
385                 "/somewhere/app/foo/split_0.apk", "x86_64", mIsInDalvikCache)));
386         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
387                 "/somewhere/app/foo/split_0.apk", "x86", mIsInDalvikCache)));
388 
389         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
390                 PKG_NAME_1, "/somewhere/app/foo/base.apk", "x86_64")));
391         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
392                 PKG_NAME_1, "/somewhere/app/foo/base.apk", "x86")));
393         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
394                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "x86_64")));
395         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
396                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "x86")));
397 
398         // We assume that the ISA got from `DexUseManagerLocal` is already the translated one.
399         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
400                 "/data/user/0/foo/1.apk", "x86_64", false /* isInDalvikCache */)));
401         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
402                 "/data/user/0/foo/not_found.apk", "x86_64", false /* isInDalvikCache */)));
403 
404         // Verify that there are no more calls than the ones above.
405         verify(mArtd, times(6)).deleteArtifacts(any());
406         verify(mArtd, times(4)).deleteRuntimeArtifacts(any());
407     }
408 
409     @Test(expected = IllegalArgumentException.class)
testdeleteDexoptArtifactsPackageNotFound()410     public void testdeleteDexoptArtifactsPackageNotFound() throws Exception {
411         when(mSnapshot.getPackageState(anyString())).thenReturn(null);
412 
413         mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
414     }
415 
416     @Test(expected = IllegalArgumentException.class)
testdeleteDexoptArtifactsNoPackage()417     public void testdeleteDexoptArtifactsNoPackage() throws Exception {
418         when(mPkgState1.getAndroidPackage()).thenReturn(null);
419 
420         mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
421     }
422 
423     @Test
testGetDexoptStatus()424     public void testGetDexoptStatus() throws Exception {
425         doReturn(createGetDexoptStatusResult("speed", "compilation-reason-0",
426                          "location-debug-string-0", ArtifactsLocation.NEXT_TO_DEX))
427                 .when(mArtd)
428                 .getDexoptStatus("/somewhere/app/foo/base.apk", "arm64", "PCL[]");
429         doReturn(createGetDexoptStatusResult("speed-profile", "compilation-reason-1",
430                          "location-debug-string-1", ArtifactsLocation.NEXT_TO_DEX))
431                 .when(mArtd)
432                 .getDexoptStatus("/somewhere/app/foo/base.apk", "arm", "PCL[]");
433         doReturn(createGetDexoptStatusResult("verify", "compilation-reason-2",
434                          "location-debug-string-2", ArtifactsLocation.NEXT_TO_DEX))
435                 .when(mArtd)
436                 .getDexoptStatus("/somewhere/app/foo/split_0.apk", "arm64", "PCL[base.apk]");
437         doReturn(createGetDexoptStatusResult("extract", "compilation-reason-3",
438                          "location-debug-string-3", ArtifactsLocation.NEXT_TO_DEX))
439                 .when(mArtd)
440                 .getDexoptStatus("/somewhere/app/foo/split_0.apk", "arm", "PCL[base.apk]");
441         doReturn(createGetDexoptStatusResult(
442                          "run-from-apk", "unknown", "unknown", ArtifactsLocation.NEXT_TO_DEX))
443                 .when(mArtd)
444                 .getDexoptStatus("/data/user/0/foo/1.apk", "arm64", "CLC");
445         doReturn(createGetDexoptStatusResult(
446                          "unknown", "unknown", "error", ArtifactsLocation.NONE_OR_ERROR))
447                 .when(mArtd)
448                 .getDexoptStatus("/data/user/0/foo/not_found.apk", "arm64", "CLC");
449 
450         DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
451 
452         assertThat(result.getDexContainerFileDexoptStatuses())
453                 .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptStatus>deepEquality())
454                 .containsExactly(
455                         DexContainerFileDexoptStatus.create("/somewhere/app/foo/base.apk",
456                                 true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
457                                 "speed", "compilation-reason-0", "location-debug-string-0"),
458                         DexContainerFileDexoptStatus.create("/somewhere/app/foo/base.apk",
459                                 true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
460                                 "speed-profile", "compilation-reason-1", "location-debug-string-1"),
461                         DexContainerFileDexoptStatus.create("/somewhere/app/foo/split_0.apk",
462                                 true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
463                                 "verify", "compilation-reason-2", "location-debug-string-2"),
464                         DexContainerFileDexoptStatus.create("/somewhere/app/foo/split_0.apk",
465                                 true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
466                                 "extract", "compilation-reason-3", "location-debug-string-3"),
467                         DexContainerFileDexoptStatus.create("/data/user/0/foo/1.apk",
468                                 false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
469                                 "run-from-apk", "unknown", "unknown"),
470                         DexContainerFileDexoptStatus.create("/data/user/0/foo/not_found.apk",
471                                 false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
472                                 "unknown", "unknown", "error"));
473     }
474 
475     @Test(expected = IllegalArgumentException.class)
testGetDexoptStatusPackageNotFound()476     public void testGetDexoptStatusPackageNotFound() throws Exception {
477         when(mSnapshot.getPackageState(anyString())).thenReturn(null);
478 
479         mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
480     }
481 
482     @Test(expected = IllegalArgumentException.class)
testGetDexoptStatusNoPackage()483     public void testGetDexoptStatusNoPackage() throws Exception {
484         when(mPkgState1.getAndroidPackage()).thenReturn(null);
485 
486         mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
487     }
488 
489     @Test
testGetDexoptStatusNonFatalError()490     public void testGetDexoptStatusNonFatalError() throws Exception {
491         when(mArtd.getDexoptStatus(any(), any(), any()))
492                 .thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message"));
493 
494         DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
495 
496         List<DexContainerFileDexoptStatus> statuses = result.getDexContainerFileDexoptStatuses();
497         assertThat(statuses.size()).isEqualTo(6);
498 
499         for (DexContainerFileDexoptStatus status : statuses) {
500             assertThat(status.getCompilerFilter()).isEqualTo("error");
501             assertThat(status.getCompilationReason()).isEqualTo("error");
502             assertThat(status.getLocationDebugString()).isEqualTo("some error message");
503         }
504     }
505 
506     @Test
testClearAppProfiles()507     public void testClearAppProfiles() throws Exception {
508         mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
509 
510         verify(mArtd).deleteProfile(
511                 deepEq(AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary")));
512         verify(mArtd).deleteProfile(deepEq(
513                 AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
514         verify(mArtd).deleteProfile(deepEq(
515                 AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary")));
516 
517         verify(mArtd).deleteProfile(
518                 deepEq(AidlUtils.buildProfilePathForSecondaryRefAsInput("/data/user/0/foo/1.apk")));
519         verify(mArtd).deleteProfile(
520                 deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
521 
522         verify(mArtd).deleteProfile(deepEq(AidlUtils.buildProfilePathForSecondaryRefAsInput(
523                 "/data/user/0/foo/not_found.apk")));
524         verify(mArtd).deleteProfile(deepEq(
525                 AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/not_found.apk")));
526     }
527 
528     @Test(expected = IllegalArgumentException.class)
testClearAppProfilesPackageNotFound()529     public void testClearAppProfilesPackageNotFound() throws Exception {
530         when(mSnapshot.getPackageState(anyString())).thenReturn(null);
531 
532         mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
533     }
534 
535     @Test(expected = IllegalArgumentException.class)
testClearAppProfilesNoPackage()536     public void testClearAppProfilesNoPackage() throws Exception {
537         when(mPkgState1.getAndroidPackage()).thenReturn(null);
538 
539         mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
540     }
541 
542     @Test
testDexoptPackage()543     public void testDexoptPackage() throws Exception {
544         var params = new DexoptParams.Builder("install").build();
545         var result = DexoptResult.create();
546         var cancellationSignal = new CancellationSignal();
547 
548         when(mDexoptHelper.dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params),
549                      same(cancellationSignal), any()))
550                 .thenReturn(result);
551 
552         assertThat(
553                 mArtManagerLocal.dexoptPackage(mSnapshot, PKG_NAME_1, params, cancellationSignal))
554                 .isSameInstanceAs(result);
555     }
556 
557     @Test
testResetDexoptStatus()558     public void testResetDexoptStatus() throws Exception {
559         var result = DexoptResult.create();
560         var cancellationSignal = new CancellationSignal();
561 
562         when(mDexoptHelper.dexopt(
563                      any(), deepEq(List.of(PKG_NAME_1)), any(), same(cancellationSignal), any()))
564                 .thenReturn(result);
565 
566         assertThat(mArtManagerLocal.resetDexoptStatus(mSnapshot, PKG_NAME_1, cancellationSignal))
567                 .isSameInstanceAs(result);
568 
569         verify(mArtd).deleteProfile(
570                 deepEq(AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary")));
571         verify(mArtd).deleteProfile(deepEq(
572                 AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
573         verify(mArtd).deleteProfile(deepEq(
574                 AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary")));
575 
576         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
577                 "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache)));
578         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
579                 "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache)));
580         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
581                 "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache)));
582         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
583                 "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)));
584 
585         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
586                 "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache)));
587         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
588                 "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache)));
589         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
590                 "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache)));
591         verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
592                 "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)));
593 
594         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
595                 PKG_NAME_1, "/somewhere/app/foo/base.apk", "arm64")));
596         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
597                 PKG_NAME_1, "/somewhere/app/foo/base.apk", "arm")));
598         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
599                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64")));
600         verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
601                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")));
602 
603         verify(mArtd).deleteProfile(
604                 deepEq(AidlUtils.buildProfilePathForSecondaryRefAsInput("/data/user/0/foo/1.apk")));
605         verify(mArtd).deleteProfile(
606                 deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
607 
608         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput(
609                 "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
610     }
611 
612     @Test
testDexoptPackages()613     public void testDexoptPackages() throws Exception {
614         var dexoptResult = DexoptResult.create();
615         var cancellationSignal = new CancellationSignal();
616         when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_2)).thenReturn(CURRENT_TIME_MS);
617         simulateStorageLow();
618 
619         // It should use the default package list and params. The list is sorted by last active
620         // time in descending order.
621         doReturn(dexoptResult)
622                 .when(mDexoptHelper)
623                 .dexopt(any(), deepEq(List.of(PKG_NAME_2, PKG_NAME_1)),
624                         argThat(params -> params.getReason().equals("bg-dexopt")),
625                         same(cancellationSignal), any(), any(), any());
626 
627         assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
628                            null /* processCallbackExecutor */, null /* processCallback */))
629                 .isEqualTo(Map.of(ArtFlags.PASS_MAIN, dexoptResult));
630 
631         // Nothing to downgrade.
632         verify(mDexoptHelper, never())
633                 .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
634                         any(), any(), any(), any());
635     }
636 
637     @Test
testDexoptPackagesRecentlyInstalled()638     public void testDexoptPackagesRecentlyInstalled() throws Exception {
639         // The package is recently installed but hasn't been used.
640         PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
641         when(userState.getFirstInstallTimeMillis()).thenReturn(RECENT_TIME_MS);
642         when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(0l);
643         simulateStorageLow();
644 
645         var result = DexoptResult.create();
646         var cancellationSignal = new CancellationSignal();
647 
648         // PKG_NAME_1 should be dexopted.
649         doReturn(result)
650                 .when(mDexoptHelper)
651                 .dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2),
652                         argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
653                         any(), any());
654 
655         mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
656                 null /* processCallbackExecutor */, null /* processCallback */);
657 
658         // PKG_NAME_1 should not be downgraded.
659         verify(mDexoptHelper, never())
660                 .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
661                         any(), any(), any(), any());
662     }
663 
664     @Test
testDexoptPackagesInactive()665     public void testDexoptPackagesInactive() throws Exception {
666         // PKG_NAME_1 is neither recently installed nor recently used.
667         PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
668         when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
669         when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
670         simulateStorageLow();
671 
672         var mainResult = DexoptResult.create();
673         var downgradeResult = DexoptResult.create();
674         var cancellationSignal = new CancellationSignal();
675 
676         // PKG_NAME_1 should not be dexopted.
677         doReturn(mainResult)
678                 .when(mDexoptHelper)
679                 .dexopt(any(), deepEq(List.of(PKG_NAME_2)),
680                         argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
681                         any(), any());
682 
683         // PKG_NAME_1 should be downgraded.
684         doReturn(downgradeResult)
685                 .when(mDexoptHelper)
686                 .dexopt(any(), deepEq(List.of(PKG_NAME_1)),
687                         argThat(params -> params.getReason().equals("inactive")), any(), any(),
688                         any(), any());
689 
690         assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
691                            null /* processCallbackExecutor */, null /* processCallback */))
692                 .isEqualTo(Map.of(
693                         ArtFlags.PASS_DOWNGRADE, downgradeResult, ArtFlags.PASS_MAIN, mainResult));
694     }
695 
696     @Test
testDexoptPackagesInactiveStorageNotLow()697     public void testDexoptPackagesInactiveStorageNotLow() throws Exception {
698         // PKG_NAME_1 is neither recently installed nor recently used.
699         PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
700         when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
701         when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
702 
703         var result = DexoptResult.create();
704         var cancellationSignal = new CancellationSignal();
705 
706         // PKG_NAME_1 should not be dexopted.
707         doReturn(result)
708                 .when(mDexoptHelper)
709                 .dexopt(any(), deepEq(List.of(PKG_NAME_2)),
710                         argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
711                         any(), any());
712 
713         mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
714                 null /* processCallbackExecutor */, null /* processCallback */);
715 
716         // PKG_NAME_1 should not be downgraded because the storage is not low.
717         verify(mDexoptHelper, never())
718                 .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
719                         any(), any(), any(), any());
720     }
721 
722     @Test
testDexoptPackagesFirstBoot()723     public void testDexoptPackagesFirstBoot() throws Exception {
724         // On first-boot all packages haven't been used and first install time is
725         // 0 which simulates case of system time being advanced by
726         // AlarmManagerService after package installation
727         lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(0l);
728 
729         var result = DexoptResult.create();
730         var cancellationSignal = new CancellationSignal();
731 
732         // PKG_NAME_1 and PKG_NAME_2 should be dexopted.
733         doReturn(result)
734                 .when(mDexoptHelper)
735                 .dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2),
736                         argThat(params -> params.getReason().equals("first-boot")), any(), any(),
737                         any(), any());
738 
739         mArtManagerLocal.dexoptPackages(mSnapshot, "first-boot", cancellationSignal,
740                 null /* processCallbackExecutor */, null /* processCallback */);
741     }
742 
743     @Test
testDexoptPackagesBootAfterMainlineUpdate()744     public void testDexoptPackagesBootAfterMainlineUpdate() throws Exception {
745         var result = DexoptResult.create();
746         var cancellationSignal = new CancellationSignal();
747 
748         lenient().when(mInjector.isSystemUiPackage(PKG_NAME_1)).thenReturn(true);
749         lenient().when(mInjector.isLauncherPackage(PKG_NAME_2)).thenReturn(true);
750 
751         // It should dexopt the system UI and the launcher.
752         when(mDexoptHelper.dexopt(
753                      any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), any(), any(), any(), any(), any()))
754                 .thenReturn(result);
755 
756         mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal,
757                 null /* processCallbackExecutor */, null /* processCallback */);
758     }
759 
760     @Test
testDexoptPackagesBootAfterMainlineUpdatePackagesNotFound()761     public void testDexoptPackagesBootAfterMainlineUpdatePackagesNotFound() throws Exception {
762         var result = DexoptResult.create();
763         var cancellationSignal = new CancellationSignal();
764         // PKG_NAME_1 is neither recently installed nor recently used.
765         PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
766         lenient().when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
767         lenient()
768                 .when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1))
769                 .thenReturn(NOT_RECENT_TIME_MS);
770         simulateStorageLow();
771 
772         // It should dexopt the system UI and the launcher, but they are not found.
773         when(mDexoptHelper.dexopt(any(), deepEq(List.of()), any(), any(), any(), any(), any()))
774                 .thenReturn(result);
775 
776         mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal,
777                 null /* processCallbackExecutor */, null /* processCallback */);
778 
779         // It should never downgrade apps, even if the storage is low.
780         verify(mDexoptHelper, never())
781                 .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
782                         any(), any(), any(), any());
783     }
784 
785     @Test
testDexoptPackagesOverride()786     public void testDexoptPackagesOverride() throws Exception {
787         // PKG_NAME_1 is neither recently installed nor recently used.
788         PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
789         when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
790         when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
791         simulateStorageLow();
792 
793         var params = new DexoptParams.Builder("bg-dexopt").build();
794         var result = DexoptResult.create();
795         var cancellationSignal = new CancellationSignal();
796 
797         mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
798                 (snapshot, reason, defaultPackages, builder, passedSignal) -> {
799                     assertThat(reason).isEqualTo("bg-dexopt");
800                     assertThat(defaultPackages).containsExactly(PKG_NAME_2);
801                     assertThat(passedSignal).isSameInstanceAs(cancellationSignal);
802                     builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params);
803                 });
804 
805         // It should use the overridden package list and params.
806         doReturn(result)
807                 .when(mDexoptHelper)
808                 .dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params), any(), any(), any(),
809                         any());
810 
811         mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
812                 null /* processCallbackExecutor */, null /* processCallback */);
813 
814         // It should not downgrade PKG_NAME_1 because it's in the overridden package list. It should
815         // not downgrade PKG_NAME_2 either because it's not an inactive package.
816         verify(mDexoptHelper, never())
817                 .dexopt(any(), any(), argThat(params2 -> params2.getReason().equals("inactive")),
818                         any(), any(), any(), any());
819     }
820 
821     @Test
testDexoptPackagesOverrideCleared()822     public void testDexoptPackagesOverrideCleared() throws Exception {
823         var params = new DexoptParams.Builder("bg-dexopt").build();
824         var result = DexoptResult.create();
825         var cancellationSignal = new CancellationSignal();
826 
827         mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
828                 (snapshot, reason, defaultPackages, builder, passedSignal) -> {
829                     builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params);
830                 });
831         mArtManagerLocal.clearBatchDexoptStartCallback();
832 
833         // It should use the default package list and params.
834         when(mDexoptHelper.dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), not(same(params)),
835                      same(cancellationSignal), any(), any(), any()))
836                 .thenReturn(result);
837 
838         assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
839                            null /* processCallbackExecutor */, null /* processCallback */))
840                 .isEqualTo(Map.of(ArtFlags.PASS_MAIN, result));
841     }
842 
843     @Test
testDexoptPackagesSupplementaryPass()844     public void testDexoptPackagesSupplementaryPass() throws Exception {
845         // The supplementary pass should only try dexopting PKG_NAME_2.
846         var mainResult = DexoptResult.create("speed-profile", "bg-dexopt",
847                 List.of(PackageDexoptResult.create(PKG_NAME_1,
848                                 List.of(DexContainerFileDexoptResult.create("dex-file-1",
849                                                 true /* isPrimaryAbi */, "arm64", "speed-profile",
850                                                 DexoptResult.DEXOPT_PERFORMED),
851                                         DexContainerFileDexoptResult.create("dex-file-2",
852                                                 true /* isPrimaryAbi */, "arm64", "speed",
853                                                 DexoptResult.DEXOPT_SKIPPED)),
854                                 null /* packageLevelStatus */),
855                         PackageDexoptResult.create(PKG_NAME_2,
856                                 List.of(DexContainerFileDexoptResult.create("dex-file-1",
857                                                 true /* isPrimaryAbi */, "arm64", "speed-profile",
858                                                 DexoptResult.DEXOPT_PERFORMED),
859                                         DexContainerFileDexoptResult.create("dex-file-2",
860                                                 true /* isPrimaryAbi */, "arm64", "speed-profile",
861                                                 DexoptResult.DEXOPT_SKIPPED)),
862                                 null /* packageLevelStatus */)));
863         var supplementaryResult = DexoptResult.create();
864         var cancellationSignal = new CancellationSignal();
865 
866         // The main pass.
867         doReturn(mainResult)
868                 .when(mDexoptHelper)
869                 .dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2),
870                         argThat(params
871                                 -> params.getReason().equals("bg-dexopt")
872                                         && (params.getFlags() & ArtFlags.FLAG_FORCE_MERGE_PROFILE)
873                                                 == 0),
874                         any(), any(), any(), any());
875 
876         // The supplementary pass.
877         doReturn(supplementaryResult)
878                 .when(mDexoptHelper)
879                 .dexopt(any(), inAnyOrder(PKG_NAME_2),
880                         argThat(params
881                                 -> params.getReason().equals("bg-dexopt")
882                                         && (params.getFlags() & ArtFlags.FLAG_FORCE_MERGE_PROFILE)
883                                                 != 0),
884                         any(), any(), any(), any());
885 
886         assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
887                            null /* processCallbackExecutor */, null /* processCallback */))
888                 .isEqualTo(Map.of(ArtFlags.PASS_MAIN, mainResult, ArtFlags.PASS_SUPPLEMENTARY,
889                         supplementaryResult));
890     }
891 
892     @Test(expected = IllegalStateException.class)
testDexoptPackagesOverrideReasonChanged()893     public void testDexoptPackagesOverrideReasonChanged() throws Exception {
894         var params = new DexoptParams.Builder("first-boot").build();
895         var cancellationSignal = new CancellationSignal();
896 
897         mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
898                 (snapshot, reason, defaultPackages, builder, passedSignal) -> {
899                     builder.setDexoptParams(params);
900                 });
901 
902         mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
903                 null /* processCallbackExecutor */, null /* processCallback */);
904     }
905 
906     @Test
testDexoptPackagesWithParams()907     public void testDexoptPackagesWithParams() throws Exception {
908         var dexoptResult = DexoptResult.create();
909         var cancellationSignal = new CancellationSignal();
910 
911         // It should only dexopt PKG_NAME_1 with "speed" as specified by the params.
912         doReturn(dexoptResult)
913                 .when(mDexoptHelper)
914                 .dexopt(any(), deepEq(List.of(PKG_NAME_1)),
915                         argThat(params
916                                 -> params.getReason().equals("ab-ota")
917                                         && params.getCompilerFilter().equals("speed")),
918                         same(cancellationSignal), any(), any(), any());
919 
920         BatchDexoptParams params = new BatchDexoptParams
921                                            .Builder(List.of(PKG_NAME_1),
922                                                    new DexoptParams.Builder("ab-ota")
923                                                            .setCompilerFilter("speed")
924                                                            .build())
925                                            .build();
926         assertThat(
927                 mArtManagerLocal.dexoptPackagesWithParams(mSnapshot, "ab-ota", cancellationSignal,
928                         null /* processCallbackExecutor */, null /* processCallback */, params))
929                 .isEqualTo(Map.of(ArtFlags.PASS_MAIN, dexoptResult));
930     }
931 
932     @Test
testSnapshotAppProfile()933     public void testSnapshotAppProfile() throws Exception {
934         var options = new MergeProfileOptions();
935         options.forceMerge = true;
936         options.forBootImage = false;
937 
938         File tempFile = File.createTempFile("primary", ".prof");
939         tempFile.deleteOnExit();
940 
941         ProfilePath refProfile =
942                 AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary");
943         String dexPath = "/somewhere/app/foo/base.apk";
944 
945         when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true);
946 
947         when(mArtd.mergeProfiles(deepEq(List.of(refProfile,
948                                          AidlUtils.buildProfilePathForPrimaryCur(
949                                                  0 /* userId */, PKG_NAME_1, "primary"),
950                                          AidlUtils.buildProfilePathForPrimaryCur(
951                                                  1 /* userId */, PKG_NAME_1, "primary"))),
952                      isNull(),
953                      deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "primary",
954                              Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */,
955                              false /* isPreReboot */)),
956                      deepEq(List.of(dexPath)), deepEq(options)))
957                 .thenAnswer(invocation -> {
958                     try (var writer = new FileWriter(tempFile)) {
959                         writer.write("snapshot");
960                     } catch (IOException e) {
961                         throw new RuntimeException(e);
962                     }
963                     var output = invocation.<OutputProfile>getArgument(2);
964                     output.profilePath.tmpPath = tempFile.getPath();
965                     return true;
966                 });
967 
968         ParcelFileDescriptor fd =
969                 mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
970 
971         verify(mArtd).deleteProfile(
972                 argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath())));
973 
974         assertThat(fd.getStatSize()).isGreaterThan(0);
975         try (InputStream inputStream = new AutoCloseInputStream(fd)) {
976             String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
977             assertThat(contents).isEqualTo("snapshot");
978         }
979     }
980 
981     @Test
testSnapshotAppProfileFromDm()982     public void testSnapshotAppProfileFromDm() throws Exception {
983         String tempPathForRef = "/temp/path/for/ref";
984         File tempFileForSnapshot = File.createTempFile("primary", ".prof");
985         tempFileForSnapshot.deleteOnExit();
986 
987         ProfilePath refProfile =
988                 AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary");
989         String dexPath = "/somewhere/app/foo/base.apk";
990 
991         // Simulate that the reference profile doesn't exist.
992         when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
993 
994         // The DM file is usable.
995         when(mArtd.copyAndRewriteProfile(
996                      deepEq(AidlUtils.buildProfilePathForDm(dexPath)), any(), eq(dexPath)))
997                 .thenAnswer(invocation -> {
998                     var output = invocation.<OutputProfile>getArgument(1);
999                     output.profilePath.tmpPath = tempPathForRef;
1000                     return TestingUtils.createCopyAndRewriteProfileSuccess();
1001                 });
1002 
1003         // Verify that the reference file initialized from the DM file is used.
1004         when(mArtd.mergeProfiles(
1005                      argThat(profiles
1006                              -> profiles.stream().anyMatch(profile
1007                                      -> profile.getTag() == ProfilePath.tmpProfilePath
1008                                              && profile.getTmpProfilePath().tmpPath.equals(
1009                                                      tempPathForRef))),
1010                      isNull(), any(), deepEq(List.of(dexPath)), any()))
1011                 .thenAnswer(invocation -> {
1012                     var output = invocation.<OutputProfile>getArgument(2);
1013                     output.profilePath.tmpPath = tempFileForSnapshot.getPath();
1014                     return true;
1015                 });
1016 
1017         ParcelFileDescriptor fd =
1018                 mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
1019 
1020         verify(mArtd).deleteProfile(argThat(profile
1021                 -> profile.getTmpProfilePath().tmpPath.equals(tempFileForSnapshot.getPath())));
1022         verify(mArtd).deleteProfile(
1023                 argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempPathForRef)));
1024     }
1025 
1026     @Test
testSnapshotAppProfileFromEmbeddedProfile()1027     public void testSnapshotAppProfileFromEmbeddedProfile() throws Exception {
1028         ProfilePath refProfile =
1029                 AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary");
1030         String dexPath = "/somewhere/app/foo/base.apk";
1031 
1032         // Simulate that the reference profile doesn't exist.
1033         when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
1034 
1035         // Verify that the embedded profile is used.
1036         when(mArtd.copyAndRewriteEmbeddedProfile(any(), eq(dexPath)))
1037                 .thenReturn(TestingUtils.createCopyAndRewriteProfileSuccess());
1038 
1039         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
1040 
1041         mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
1042     }
1043 
1044     @Test
testSnapshotAppProfileDisableEmbeddedProfile()1045     public void testSnapshotAppProfileDisableEmbeddedProfile() throws Exception {
1046         ProfilePath refProfile =
1047                 AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary");
1048         String dexPath = "/somewhere/app/foo/base.apk";
1049 
1050         // Simulate that the reference profile doesn't exist.
1051         when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
1052 
1053         // The embedded profile is disabled.
1054         var config = DexMetadataConfig.newBuilder().setEnableEmbeddedProfile(false).build();
1055         String dmPath = TestingUtils.createTempZipWithEntry("config.pb", config.toByteArray());
1056         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(any());
1057 
1058         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
1059 
1060         mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
1061 
1062         // Verify that the embedded profile is not used.
1063         verify(mArtd, never()).copyAndRewriteEmbeddedProfile(any(), eq(dexPath));
1064     }
1065 
1066     @Test
testSnapshotAppProfileSplit()1067     public void testSnapshotAppProfileSplit() throws Exception {
1068         ProfilePath refProfile =
1069                 AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "split_0.split");
1070         String dexPath = "/somewhere/app/foo/split_0.apk";
1071 
1072         when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true);
1073 
1074         when(mArtd.mergeProfiles(deepEq(List.of(refProfile,
1075                                          AidlUtils.buildProfilePathForPrimaryCur(
1076                                                  0 /* userId */, PKG_NAME_1, "split_0.split"),
1077                                          AidlUtils.buildProfilePathForPrimaryCur(
1078                                                  1 /* userId */, PKG_NAME_1, "split_0.split"))),
1079                      isNull(),
1080                      deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "split_0.split",
1081                              Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */,
1082                              false /* isPreReboot */)),
1083                      deepEq(List.of(dexPath)), any()))
1084                 .thenReturn(false);
1085 
1086         mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "split_0");
1087     }
1088 
1089     @Test
testSnapshotAppProfileEmpty()1090     public void testSnapshotAppProfileEmpty() throws Exception {
1091         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
1092 
1093         ParcelFileDescriptor fd =
1094                 mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
1095 
1096         verify(mArtd, never()).deleteProfile(any());
1097 
1098         assertThat(fd.getStatSize()).isEqualTo(0);
1099         try (InputStream inputStream = new AutoCloseInputStream(fd)) {
1100             assertThat(inputStream.readAllBytes()).isEmpty();
1101         }
1102     }
1103 
1104     @Test(expected = IllegalArgumentException.class)
testSnapshotAppProfilePackageNotFound()1105     public void testSnapshotAppProfilePackageNotFound() throws Exception {
1106         when(mSnapshot.getPackageState(anyString())).thenReturn(null);
1107 
1108         mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
1109     }
1110 
1111     @Test(expected = IllegalArgumentException.class)
testSnapshotAppProfileNoPackage()1112     public void testSnapshotAppProfileNoPackage() throws Exception {
1113         when(mPkgState1.getAndroidPackage()).thenReturn(null);
1114 
1115         mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
1116     }
1117 
1118     @Test(expected = IllegalArgumentException.class)
testSnapshotAppProfileSplitNotFound()1119     public void testSnapshotAppProfileSplitNotFound() throws Exception {
1120         mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "non-existent-split");
1121     }
1122 
1123     @Test
testDumpAppProfile()1124     public void testDumpAppProfile() throws Exception {
1125         var options = new MergeProfileOptions();
1126         options.dumpOnly = true;
1127 
1128         when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options)))
1129                 .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
1130 
1131         ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
1132                 mSnapshot, PKG_NAME_1, null /* splitName */, false /* dumpClassesAndMethods */);
1133     }
1134 
1135     @Test
testDumpAppProfileDumpClassesAndMethods()1136     public void testDumpAppProfileDumpClassesAndMethods() throws Exception {
1137         var options = new MergeProfileOptions();
1138         options.dumpClassesAndMethods = true;
1139 
1140         when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options)))
1141                 .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
1142 
1143         ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
1144                 mSnapshot, PKG_NAME_1, null /* splitName */, true /* dumpClassesAndMethods */);
1145     }
1146 
1147     @Test
testSnapshotBootImageProfile()1148     public void testSnapshotBootImageProfile() throws Exception {
1149         // `lenient()` is required to allow mocking the same method multiple times.
1150         lenient().when(Constants.getenv("BOOTCLASSPATH")).thenReturn("bcp0:bcp1");
1151         lenient().when(Constants.getenv("SYSTEMSERVERCLASSPATH")).thenReturn("sscp0:sscp1");
1152         lenient().when(Constants.getenv("STANDALONE_SYSTEMSERVER_JARS")).thenReturn("sssj0:sssj1");
1153 
1154         var options = new MergeProfileOptions();
1155         options.forceMerge = true;
1156         options.forBootImage = true;
1157 
1158         when(mArtd.mergeProfiles(
1159                      inAnyOrderDeepEquals(
1160                              AidlUtils.buildProfilePathForPrimaryRefAsInput("android", "primary"),
1161                              AidlUtils.buildProfilePathForPrimaryCur(
1162                                      0 /* userId */, "android", "primary"),
1163                              AidlUtils.buildProfilePathForPrimaryCur(
1164                                      1 /* userId */, "android", "primary"),
1165                              AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary"),
1166                              AidlUtils.buildProfilePathForPrimaryCur(
1167                                      0 /* userId */, PKG_NAME_1, "primary"),
1168                              AidlUtils.buildProfilePathForPrimaryCur(
1169                                      1 /* userId */, PKG_NAME_1, "primary"),
1170                              AidlUtils.buildProfilePathForPrimaryRefAsInput(
1171                                      PKG_NAME_1, "split_0.split"),
1172                              AidlUtils.buildProfilePathForPrimaryCur(
1173                                      0 /* userId */, PKG_NAME_1, "split_0.split"),
1174                              AidlUtils.buildProfilePathForPrimaryCur(
1175                                      1 /* userId */, PKG_NAME_1, "split_0.split"),
1176                              AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_2, "primary"),
1177                              AidlUtils.buildProfilePathForPrimaryCur(
1178                                      0 /* userId */, PKG_NAME_2, "primary"),
1179                              AidlUtils.buildProfilePathForPrimaryCur(
1180                                      1 /* userId */, PKG_NAME_2, "primary"),
1181                              AidlUtils.buildProfilePathForPrimaryRefAsInput(
1182                                      PKG_NAME_HIBERNATING, "primary"),
1183                              AidlUtils.buildProfilePathForPrimaryCur(
1184                                      0 /* userId */, PKG_NAME_HIBERNATING, "primary"),
1185                              AidlUtils.buildProfilePathForPrimaryCur(
1186                                      1 /* userId */, PKG_NAME_HIBERNATING, "primary")),
1187                      isNull(),
1188                      deepEq(AidlUtils.buildOutputProfileForPrimary("android", "primary",
1189                              Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */,
1190                              false /* isPreReboot */)),
1191                      deepEq(List.of("bcp0", "bcp1", "sscp0", "sscp1", "sssj0", "sssj1")),
1192                      deepEq(options)))
1193                 .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
1194 
1195         mArtManagerLocal.snapshotBootImageProfile(mSnapshot);
1196     }
1197 
1198     @Test
testOnBoot()1199     public void testOnBoot() throws Exception {
1200         var progressCallbackExecutor = mock(Executor.class);
1201         var progressCallback = mock(Consumer.class);
1202 
1203         when(mDexoptHelper.dexopt(any(), any(),
1204                      argThat(params -> params.getReason().equals(ReasonMapping.REASON_FIRST_BOOT)),
1205                      any(), any(), same(progressCallbackExecutor), same(progressCallback)))
1206                 .thenReturn(DexoptResult.create());
1207 
1208         mArtManagerLocal.onBoot(
1209                 ReasonMapping.REASON_FIRST_BOOT, progressCallbackExecutor, progressCallback);
1210     }
1211 
1212     @Test
testOnBootNoProgressCallback()1213     public void testOnBootNoProgressCallback() throws Exception {
1214         when(mDexoptHelper.dexopt(any(), any(),
1215                      argThat(params -> params.getReason().equals(ReasonMapping.REASON_FIRST_BOOT)),
1216                      any(), any(), isNull(), isNull()))
1217                 .thenReturn(DexoptResult.create());
1218 
1219         mArtManagerLocal.onBoot(ReasonMapping.REASON_FIRST_BOOT,
1220                 null /* progressCallbackExecutor */, null /* progressCallback */);
1221     }
1222 
1223     @Test
testCleanupKeepPreRebootStagedFiles()1224     public void testCleanupKeepPreRebootStagedFiles() throws Exception {
1225         when(mPreRebootDexoptJob.hasStarted()).thenReturn(true);
1226         testCleanup(true /* keepPreRebootStagedFiles */);
1227     }
1228 
1229     @Test
testCleanupRemovePreRebootStagedFiles()1230     public void testCleanupRemovePreRebootStagedFiles() throws Exception {
1231         when(mPreRebootDexoptJob.hasStarted()).thenReturn(false);
1232         testCleanup(false /* keepPreRebootStagedFiles */);
1233     }
1234 
testCleanup(boolean keepPreRebootStagedFiles)1235     private void testCleanup(boolean keepPreRebootStagedFiles) throws Exception {
1236         // It should keep all artifacts, but not runtime images.
1237         doReturn(createGetDexoptStatusResult(
1238                          "speed-profile", "bg-dexopt", "location", ArtifactsLocation.NEXT_TO_DEX))
1239                 .when(mArtd)
1240                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm64"), any());
1241         doReturn(createGetDexoptStatusResult(
1242                          "verify", "cmdline", "location", ArtifactsLocation.NEXT_TO_DEX))
1243                 .when(mArtd)
1244                 .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
1245 
1246         // It should keep all artifacts and runtime images.
1247         doReturn(createGetDexoptStatusResult(
1248                          "verify", "bg-dexopt", "location", ArtifactsLocation.DALVIK_CACHE))
1249                 .when(mArtd)
1250                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm64"), any());
1251 
1252         // It should only keep VDEX files and runtime images.
1253         doReturn(createGetDexoptStatusResult(
1254                          "verify", "vdex", "location", ArtifactsLocation.NEXT_TO_DEX))
1255                 .when(mArtd)
1256                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm"), any());
1257 
1258         // It should not keep any artifacts or runtime images.
1259         doReturn(createGetDexoptStatusResult(
1260                          "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR))
1261                 .when(mArtd)
1262                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm"), any());
1263 
1264         when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1));
1265         mArtManagerLocal.cleanup(mSnapshot);
1266 
1267         verify(mArtd).cleanup(
1268                 inAnyOrderDeepEquals(
1269                         AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary"),
1270                         AidlUtils.buildProfilePathForPrimaryCur(
1271                                 0 /* userId */, PKG_NAME_1, "primary"),
1272                         AidlUtils.buildProfilePathForPrimaryCur(
1273                                 1 /* userId */, PKG_NAME_1, "primary"),
1274                         AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "split_0.split"),
1275                         AidlUtils.buildProfilePathForPrimaryCur(
1276                                 0 /* userId */, PKG_NAME_1, "split_0.split"),
1277                         AidlUtils.buildProfilePathForPrimaryCur(
1278                                 1 /* userId */, PKG_NAME_1, "split_0.split"),
1279                         AidlUtils.buildProfilePathForSecondaryRefAsInput("/data/user/0/foo/1.apk"),
1280                         AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")),
1281                 inAnyOrderDeepEquals(
1282                         AidlUtils.buildArtifactsPathAsInput("/somewhere/app/foo/base.apk", "arm64",
1283                                 false /* isInDalvikCache */),
1284                         AidlUtils.buildArtifactsPathAsInput(
1285                                 "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */),
1286                         AidlUtils.buildArtifactsPathAsInput("/somewhere/app/foo/split_0.apk",
1287                                 "arm64", true /* isInDalvikCache */)),
1288                 inAnyOrderDeepEquals(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput(
1289                         "/somewhere/app/foo/split_0.apk", "arm", false /* isInDalvikCache */))),
1290                 inAnyOrderDeepEquals() /* sdmSdcFilesToKeep */,
1291                 inAnyOrderDeepEquals(AidlUtils.buildRuntimeArtifactsPath(
1292                                              PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64"),
1293                         AidlUtils.buildRuntimeArtifactsPath(
1294                                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")),
1295                 eq(keepPreRebootStagedFiles));
1296     }
1297 
1298     @Test
testCleanupDmAndSdm()1299     public void testCleanupDmAndSdm() throws Exception {
1300         when(mPreRebootDexoptJob.hasStarted()).thenReturn(false);
1301 
1302         // It should keep the SDM file, but not runtime images.
1303         doReturn(createGetDexoptStatusResult(
1304                          "speed-profile", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX))
1305                 .when(mArtd)
1306                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm64"), any());
1307 
1308         // It should keep the SDM file, but not runtime images.
1309         doReturn(createGetDexoptStatusResult(
1310                          "speed-profile", "cloud", "location", ArtifactsLocation.SDM_DALVIK_CACHE))
1311                 .when(mArtd)
1312                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm"), any());
1313 
1314         // It should keep the SDM file and runtime images.
1315         doReturn(createGetDexoptStatusResult(
1316                          "verify", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX))
1317                 .when(mArtd)
1318                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm64"), any());
1319 
1320         // It should only keep runtime images.
1321         doReturn(createGetDexoptStatusResult("verify", "vdex", "location", ArtifactsLocation.DM))
1322                 .when(mArtd)
1323                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm"), any());
1324 
1325         // This file is uninteresting in this test.
1326         doReturn(createGetDexoptStatusResult(
1327                          "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR))
1328                 .when(mArtd)
1329                 .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
1330 
1331         when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1));
1332         mArtManagerLocal.cleanup(mSnapshot);
1333 
1334         verify(mArtd).cleanup(any() /* profilesToKeep */,
1335                 inAnyOrderDeepEquals() /* artifactsToKeep */,
1336                 inAnyOrderDeepEquals() /* vdexFilesToKeep */,
1337                 inAnyOrderDeepEquals(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
1338                                              "/somewhere/app/foo/base.apk", "arm64",
1339                                              false /* isInDalvikCache */),
1340                         AidlUtils.buildSecureDexMetadataWithCompanionPaths(
1341                                 "/somewhere/app/foo/base.apk", "arm", true /* isInDalvikCache */),
1342                         AidlUtils.buildSecureDexMetadataWithCompanionPaths(
1343                                 "/somewhere/app/foo/split_0.apk", "arm64",
1344                                 false /* isInDalvikCache */)),
1345                 inAnyOrderDeepEquals(AidlUtils.buildRuntimeArtifactsPath(
1346                                              PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64"),
1347                         AidlUtils.buildRuntimeArtifactsPath(
1348                                 PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")),
1349                 eq(false) /* keepPreRebootStagedFiles */);
1350     }
1351 
1352     @Test
testGetArtManagedFileStatsSystem()1353     public void testGetArtManagedFileStatsSystem() throws Exception {
1354         testGetArtManagedFileStats(true /* isSystemOrRootOrShell */);
1355     }
1356 
1357     @Test
testGetArtManagedFileStatsNonSystem()1358     public void testGetArtManagedFileStatsNonSystem() throws Exception {
1359         testGetArtManagedFileStats(false /* isSystemOrRootOrShell */);
1360     }
1361 
testGetArtManagedFileStats(boolean isSystemOrRootOrShell)1362     private void testGetArtManagedFileStats(boolean isSystemOrRootOrShell) throws Exception {
1363         if (!isSystemOrRootOrShell) {
1364             lenient().when(mArtFileManagerInjector.isSystemOrRootOrShell()).thenReturn(false);
1365             lenient()
1366                     .when(mArtFileManagerInjector.getCallingUserHandle())
1367                     .thenReturn(UserHandle.of(0));
1368         }
1369 
1370         // The same setup as `testCleanup`, but add a secondary dex file for a different user. Its
1371         // artifacts and profiles should only be counted if the caller is system or root or shell.
1372         CheckedSecondaryDexInfo pkg1SecondaryDexInfo2 =
1373                 createSecondaryDexInfo("/data/user/1/foo/1.apk", UserHandle.of(1));
1374         lenient()
1375                 .doReturn(List.of(mPkg1SecondaryDexInfo1, pkg1SecondaryDexInfo2))
1376                 .when(mDexUseManager)
1377                 .getCheckedSecondaryDexInfo(
1378                         eq(PKG_NAME_1), eq(true) /* excludeObsoleteDexesAndLoaders */);
1379 
1380         // It should count all artifacts, but not runtime images.
1381         doReturn(createGetDexoptStatusResult(
1382                          "speed-profile", "bg-dexopt", "location", ArtifactsLocation.NEXT_TO_DEX))
1383                 .when(mArtd)
1384                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm64"), any());
1385         doReturn(createGetDexoptStatusResult(
1386                          "verify", "cmdline", "location", ArtifactsLocation.NEXT_TO_DEX))
1387                 .when(mArtd)
1388                 .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
1389 
1390         // It should count all artifacts and runtime images.
1391         doReturn(createGetDexoptStatusResult(
1392                          "verify", "bg-dexopt", "location", ArtifactsLocation.DALVIK_CACHE))
1393                 .when(mArtd)
1394                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm64"), any());
1395 
1396         // It should only count VDEX files and runtime images.
1397         doReturn(createGetDexoptStatusResult(
1398                          "verify", "vdex", "location", ArtifactsLocation.NEXT_TO_DEX))
1399                 .when(mArtd)
1400                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm"), any());
1401 
1402         // It should not count any artifacts or runtime images.
1403         doReturn(createGetDexoptStatusResult(
1404                          "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR))
1405                 .when(mArtd)
1406                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm"), any());
1407 
1408         // These are counted as TYPE_DEXOPT_ARTIFACT.
1409         doReturn(1l << 0).when(mArtd).getArtifactsSize(deepEq(AidlUtils.buildArtifactsPathAsInput(
1410                 "/somewhere/app/foo/base.apk", "arm64", false /* isInDalvikCache */)));
1411         doReturn(1l << 1).when(mArtd).getArtifactsSize(deepEq(AidlUtils.buildArtifactsPathAsInput(
1412                 "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
1413         doReturn(1l << 2).when(mArtd).getArtifactsSize(deepEq(AidlUtils.buildArtifactsPathAsInput(
1414                 "/somewhere/app/foo/split_0.apk", "arm64", true /* isInDalvikCache */)));
1415         doReturn(1l << 3).when(mArtd).getVdexFileSize(
1416                 deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput(
1417                         "/somewhere/app/foo/split_0.apk", "arm", false /* isInDalvikCache */))));
1418         doReturn(1l << 4).when(mArtd).getRuntimeArtifactsSize(
1419                 deepEq(AidlUtils.buildRuntimeArtifactsPath(
1420                         PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64")));
1421         doReturn(1l << 5).when(mArtd).getRuntimeArtifactsSize(
1422                 deepEq(AidlUtils.buildRuntimeArtifactsPath(
1423                         PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")));
1424         long expectedDexoptArtifactSize =
1425                 (1l << 0) + (1l << 1) + (1l << 2) + (1l << 3) + (1l << 4) + (1l << 5);
1426         int expectedGetArtifactsSizeCalls = 3;
1427         int expectedGetVdexFileSizeCalls = 1;
1428         int expectedGetRuntimeArtifactsSizeCalls = 2;
1429 
1430         // These are counted as TYPE_REF_PROFILE.
1431         doReturn(1l << 6).when(mArtd).getProfileSize(
1432                 deepEq(AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "primary")));
1433         doReturn(1l << 7).when(mArtd).getProfileSize(deepEq(
1434                 AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME_1, "split_0.split")));
1435         doReturn(1l << 8).when(mArtd).getProfileSize(
1436                 deepEq(AidlUtils.buildProfilePathForSecondaryRefAsInput("/data/user/0/foo/1.apk")));
1437         long expectedRefProfileSize = (1l << 6) + (1l << 7) + (1l << 8);
1438         int expectedGetProfileSizeCalls = 3;
1439 
1440         // These are counted as TYPE_CUR_PROFILE.
1441         doReturn(1l << 9).when(mArtd).getProfileSize(deepEq(
1442                 AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
1443         doReturn(1l << 10).when(mArtd).getProfileSize(
1444                 deepEq(AidlUtils.buildProfilePathForPrimaryCur(
1445                         0 /* userId */, PKG_NAME_1, "split_0.split")));
1446         doReturn(1l << 11).when(mArtd).getProfileSize(
1447                 deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
1448         long expectedCurProfileSize = (1l << 9) + (1l << 10) + (1l << 11);
1449         expectedGetProfileSizeCalls += 3;
1450 
1451         // These belong to a different user.
1452         if (isSystemOrRootOrShell) {
1453             doReturn(createGetDexoptStatusResult(
1454                              "verify", "cmdline", "location", ArtifactsLocation.NEXT_TO_DEX))
1455                     .when(mArtd)
1456                     .getDexoptStatus(eq("/data/user/1/foo/1.apk"), eq("arm64"), any());
1457 
1458             // These are counted as TYPE_DEXOPT_ARTIFACT.
1459             // Dexopt artifacts of secondary dex files.
1460             doReturn(1l << 12).when(mArtd).getArtifactsSize(
1461                     deepEq(AidlUtils.buildArtifactsPathAsInput(
1462                             "/data/user/1/foo/1.apk", "arm64", false /* isInDalvikCache */)));
1463             expectedDexoptArtifactSize += (1l << 12);
1464             expectedGetArtifactsSizeCalls += 1;
1465 
1466             // These are counted as TYPE_REF_PROFILE.
1467             // Reference profiles of secondary dex files.
1468             doReturn(1l << 13).when(mArtd).getProfileSize(deepEq(
1469                     AidlUtils.buildProfilePathForSecondaryRefAsInput("/data/user/1/foo/1.apk")));
1470             expectedRefProfileSize += (1l << 13);
1471             expectedGetProfileSizeCalls += 1;
1472 
1473             // These are counted as TYPE_CUR_PROFILE.
1474             // Current profiles of primary dex files.
1475             doReturn(1l << 14).when(mArtd).getProfileSize(
1476                     deepEq(AidlUtils.buildProfilePathForPrimaryCur(
1477                             1 /* userId */, PKG_NAME_1, "primary")));
1478             doReturn(1l << 15).when(mArtd).getProfileSize(
1479                     deepEq(AidlUtils.buildProfilePathForPrimaryCur(
1480                             1 /* userId */, PKG_NAME_1, "split_0.split")));
1481             // Current profiles of secondary dex files.
1482             doReturn(1l << 16).when(mArtd).getProfileSize(
1483                     deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/1/foo/1.apk")));
1484             expectedCurProfileSize += (1l << 14) + (1l << 15) + (1l << 16);
1485             expectedGetProfileSizeCalls += 3;
1486         }
1487 
1488         ArtManagedFileStats stats = mArtManagerLocal.getArtManagedFileStats(mSnapshot, PKG_NAME_1);
1489         assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT))
1490                 .isEqualTo(expectedDexoptArtifactSize);
1491         assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE))
1492                 .isEqualTo(expectedRefProfileSize);
1493         assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE))
1494                 .isEqualTo(expectedCurProfileSize);
1495 
1496         verify(mArtd, times(expectedGetArtifactsSizeCalls)).getArtifactsSize(any());
1497         verify(mArtd, times(expectedGetVdexFileSizeCalls)).getVdexFileSize(any());
1498         verify(mArtd, times(expectedGetRuntimeArtifactsSizeCalls)).getRuntimeArtifactsSize(any());
1499         verify(mArtd, times(expectedGetProfileSizeCalls)).getProfileSize(any());
1500     }
1501 
1502     @Test
testGetArtManagedFileStatsDmAndSdm()1503     public void testGetArtManagedFileStatsDmAndSdm() throws Exception {
1504         // It should count the SDM file, but not runtime images.
1505         doReturn(createGetDexoptStatusResult(
1506                          "speed-profile", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX))
1507                 .when(mArtd)
1508                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm64"), any());
1509 
1510         // It should count the SDM file, but not runtime images.
1511         doReturn(createGetDexoptStatusResult(
1512                          "speed-profile", "cloud", "location", ArtifactsLocation.SDM_DALVIK_CACHE))
1513                 .when(mArtd)
1514                 .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm"), any());
1515 
1516         // It should count the SDM file and runtime images.
1517         doReturn(createGetDexoptStatusResult(
1518                          "verify", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX))
1519                 .when(mArtd)
1520                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm64"), any());
1521 
1522         // It should only count runtime images.
1523         doReturn(createGetDexoptStatusResult("verify", "vdex", "location", ArtifactsLocation.DM))
1524                 .when(mArtd)
1525                 .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm"), any());
1526 
1527         // This file is uninteresting in this test.
1528         doReturn(createGetDexoptStatusResult(
1529                          "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR))
1530                 .when(mArtd)
1531                 .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
1532 
1533         // These are counted as TYPE_DEXOPT_ARTIFACT.
1534         doReturn(1l << 0).when(mArtd).getSdmFileSize(
1535                 deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
1536                         "/somewhere/app/foo/base.apk", "arm64", false /* isInDalvikCache */)));
1537         doReturn(1l << 1).when(mArtd).getSdmFileSize(
1538                 deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
1539                         "/somewhere/app/foo/base.apk", "arm", true /* isInDalvikCache */)));
1540         doReturn(1l << 2).when(mArtd).getSdmFileSize(
1541                 deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths(
1542                         "/somewhere/app/foo/split_0.apk", "arm64", false /* isInDalvikCache */)));
1543         doReturn(1l << 3).when(mArtd).getRuntimeArtifactsSize(
1544                 deepEq(AidlUtils.buildRuntimeArtifactsPath(
1545                         PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64")));
1546         doReturn(1l << 4).when(mArtd).getRuntimeArtifactsSize(
1547                 deepEq(AidlUtils.buildRuntimeArtifactsPath(
1548                         PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")));
1549 
1550         ArtManagedFileStats stats = mArtManagerLocal.getArtManagedFileStats(mSnapshot, PKG_NAME_1);
1551         assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT))
1552                 .isEqualTo((1l << 0) + (1l << 1) + (1l << 2) + (1l << 3) + (1l << 4));
1553 
1554         verify(mArtd, never()).getArtifactsSize(any());
1555         verify(mArtd, never()).getVdexFileSize(any());
1556         verify(mArtd, times(3)).getSdmFileSize(any());
1557         verify(mArtd, times(2)).getRuntimeArtifactsSize(any());
1558     }
1559 
1560     @Test
testCommitPreRebootStagedFiles()1561     public void testCommitPreRebootStagedFiles() throws Exception {
1562         when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1));
1563 
1564         mArtManagerLocal.onBoot(ReasonMapping.REASON_BOOT_AFTER_OTA,
1565                 null /* progressCallbackExecutor */, null /* progressCallback */);
1566 
1567         // It should commit files for primary dex files on boot.
1568         verify(mArtd).commitPreRebootStagedFiles(
1569                 inAnyOrderDeepEquals(
1570                         AidlUtils.buildArtifactsPathAsInput(
1571                                 "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache),
1572                         AidlUtils.buildArtifactsPathAsInput(
1573                                 "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache),
1574                         AidlUtils.buildArtifactsPathAsInput(
1575                                 "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache),
1576                         AidlUtils.buildArtifactsPathAsInput(
1577                                 "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)),
1578                 inAnyOrderDeepEquals(AidlUtils.toWritableProfilePath(
1579                                              AidlUtils.buildProfilePathForPrimaryRefAsInput(
1580                                                      PKG_NAME_1, "primary")),
1581                         AidlUtils.toWritableProfilePath(
1582                                 AidlUtils.buildProfilePathForPrimaryRefAsInput(
1583                                         PKG_NAME_1, "split_0.split"))));
1584         verify(mArtd, times(1)).commitPreRebootStagedFiles(any(), any());
1585 
1586         mArtManagerLocal.systemReady();
1587 
1588         // It should not commit anything on system ready.
1589         verify(mArtd, times(1)).commitPreRebootStagedFiles(any(), any());
1590 
1591         mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class));
1592 
1593         // It should commit files for secondary dex files on boot complete.
1594         verify(mArtd).commitPreRebootStagedFiles(
1595                 inAnyOrderDeepEquals(AidlUtils.buildArtifactsPathAsInput(
1596                         "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)),
1597                 inAnyOrderDeepEquals(AidlUtils.toWritableProfilePath(
1598                         AidlUtils.buildProfilePathForSecondaryRefAsInput(
1599                                 "/data/user/0/foo/1.apk"))));
1600         verify(mArtd, times(2)).commitPreRebootStagedFiles(any(), any());
1601     }
1602 
1603     @Test
testCommitPreRebootStagedFilesOnBootNotCalled()1604     public void testCommitPreRebootStagedFilesOnBootNotCalled() throws Exception {
1605         mArtManagerLocal.systemReady();
1606 
1607         verify(mContext, never()).registerReceiver(any(), any());
1608     }
1609 
1610     @Test
testFlushProfiles()1611     public void testFlushProfiles() throws Exception {
1612         when(mActivityManager.getRunningAppProcesses())
1613                 .thenReturn(
1614                         List.of(createProcessInfo(1000 /* pid */, "com.example.foo",
1615                                         UserHandle.of(0).getUid(APP_ID), new String[] {PKG_NAME_1},
1616                                         RunningAppProcessInfo.IMPORTANCE_FOREGROUND),
1617                                 createProcessInfo(1001 /* pid */, "com.example.foo:service1",
1618                                         UserHandle.of(0).getUid(APP_ID), new String[] {PKG_NAME_1},
1619                                         RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE),
1620                                 createProcessInfo(1002 /* pid */, "com.example.foo:service2",
1621                                         UserHandle.of(0).getUid(APP_ID), new String[] {PKG_NAME_1},
1622                                         RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE)),
1623                         // A process goes away later.
1624                         List.of(createProcessInfo(1000 /* pid */, "com.example.foo",
1625                                         UserHandle.of(0).getUid(APP_ID), new String[] {PKG_NAME_1},
1626                                         RunningAppProcessInfo.IMPORTANCE_FOREGROUND),
1627                                 createProcessInfo(1001 /* pid */, "com.example.foo:service1",
1628                                         UserHandle.of(0).getUid(APP_ID), new String[] {PKG_NAME_1},
1629                                         RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE)));
1630 
1631         PrimaryCurProfilePath profilePath = AidlUtils.buildPrimaryCurProfilePath(
1632                 0 /* userId */, PKG_NAME_1, PrimaryDexUtils.getProfileName(null /* splitName */));
1633 
1634         var notificationForPid1000 = mock(IArtdNotification.class);
1635         var notificationForPid1001 = mock(IArtdNotification.class);
1636         var notificationForPid1002 = mock(IArtdNotification.class);
1637         doReturn(notificationForPid1000)
1638                 .when(mArtd)
1639                 .initProfileSaveNotification(deepEq(profilePath), eq(1000) /* pid */);
1640         doReturn(notificationForPid1001)
1641                 .when(mArtd)
1642                 .initProfileSaveNotification(deepEq(profilePath), eq(1001) /* pid */);
1643         doReturn(notificationForPid1002)
1644                 .when(mArtd)
1645                 .initProfileSaveNotification(deepEq(profilePath), eq(1002) /* pid */);
1646         when(notificationForPid1000.wait(anyInt())).thenReturn(true);
1647         when(notificationForPid1001.wait(anyInt())).thenReturn(true);
1648 
1649         assertThat(mArtManagerLocal.flushProfiles(mSnapshot, PKG_NAME_1)).isTrue();
1650 
1651         InOrder inOrder = inOrder(mInjector, notificationForPid1000, notificationForPid1001);
1652         inOrder.verify(mInjector).kill(1000 /* pid */, OsConstants.SIGUSR1);
1653         inOrder.verify(notificationForPid1000).wait(1000 /* timeoutMs */);
1654         inOrder.verify(mInjector).kill(1001 /* pid */, OsConstants.SIGUSR1);
1655         inOrder.verify(notificationForPid1001).wait(1000 /* timeoutMs */);
1656 
1657         verify(mInjector, never()).kill(1002 /* pid */, OsConstants.SIGUSR1);
1658         verify(notificationForPid1002, never()).wait(anyInt());
1659     }
1660 
createPackage(boolean multiSplit)1661     private AndroidPackage createPackage(boolean multiSplit) {
1662         AndroidPackage pkg = mock(AndroidPackage.class);
1663 
1664         var baseSplit = mock(AndroidPackageSplit.class);
1665         lenient().when(baseSplit.getPath()).thenReturn("/somewhere/app/foo/base.apk");
1666         lenient().when(baseSplit.isHasCode()).thenReturn(true);
1667 
1668         if (multiSplit) {
1669             // split_0 has code while split_1 doesn't.
1670             var split0 = mock(AndroidPackageSplit.class);
1671             lenient().when(split0.getName()).thenReturn("split_0");
1672             lenient().when(split0.getPath()).thenReturn("/somewhere/app/foo/split_0.apk");
1673             lenient().when(split0.isHasCode()).thenReturn(true);
1674             var split1 = mock(AndroidPackageSplit.class);
1675             lenient().when(split1.getName()).thenReturn("split_1");
1676             lenient().when(split1.getPath()).thenReturn("/somewhere/app/foo/split_1.apk");
1677             lenient().when(split1.isHasCode()).thenReturn(false);
1678 
1679             lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1));
1680         } else {
1681             lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
1682         }
1683 
1684         return pkg;
1685     }
1686 
createPackageUserState()1687     private PackageUserState createPackageUserState() {
1688         PackageUserState pkgUserState = mock(PackageUserState.class);
1689         lenient().when(pkgUserState.isInstalled()).thenReturn(true);
1690         // All packages are by default pre-installed.
1691         lenient().when(pkgUserState.getFirstInstallTimeMillis()).thenReturn(0l);
1692         return pkgUserState;
1693     }
1694 
createPackageState( String packageName, boolean isDexoptable, boolean multiSplit)1695     private PackageState createPackageState(
1696             String packageName, boolean isDexoptable, boolean multiSplit) {
1697         PackageState pkgState = mock(PackageState.class);
1698 
1699         lenient().when(pkgState.getPackageName()).thenReturn(packageName);
1700         lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
1701         lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
1702         lenient().when(pkgState.getAppId()).thenReturn(APP_ID);
1703 
1704         AndroidPackage pkg = createPackage(multiSplit);
1705         lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
1706 
1707         PackageUserState pkgUserState0 = createPackageUserState();
1708         lenient().when(pkgState.getStateForUser(UserHandle.of(0))).thenReturn(pkgUserState0);
1709         PackageUserState pkgUserState1 = createPackageUserState();
1710         lenient().when(pkgState.getStateForUser(UserHandle.of(1))).thenReturn(pkgUserState1);
1711 
1712         lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(isDexoptable);
1713 
1714         return pkgState;
1715     }
1716 
createPackageStates()1717     private List<PackageState> createPackageStates() {
1718         PackageState pkgState1 =
1719                 createPackageState(PKG_NAME_1, true /* isDexoptable */, true /* multiSplit */);
1720 
1721         PackageState pkgState2 =
1722                 createPackageState(PKG_NAME_2, true /* isDexoptable */, false /* multiSplit */);
1723 
1724         // This should not be dexopted because it's hibernating. However, it should be included
1725         // when snapshotting boot image profile.
1726         PackageState pkgHibernatingState = createPackageState(
1727                 PKG_NAME_HIBERNATING, true /* isDexoptable */, false /* multiSplit */);
1728         lenient()
1729                 .when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING))
1730                 .thenReturn(true);
1731 
1732         // This should not be dexopted because it's not dexoptable.
1733         PackageState nonDexoptablePkgState = createPackageState(
1734                 "com.example.non-dexoptable", false /* isDexoptable */, false /* multiSplit */);
1735 
1736         return List.of(pkgState1, pkgState2, pkgHibernatingState, nonDexoptablePkgState);
1737     }
1738 
createGetDexoptStatusResult(String compilerFilter, String compilationReason, String locationDebugString, @ArtifactsLocation int location)1739     private GetDexoptStatusResult createGetDexoptStatusResult(String compilerFilter,
1740             String compilationReason, String locationDebugString, @ArtifactsLocation int location) {
1741         var getDexoptStatusResult = new GetDexoptStatusResult();
1742         getDexoptStatusResult.compilerFilter = compilerFilter;
1743         getDexoptStatusResult.compilationReason = compilationReason;
1744         getDexoptStatusResult.locationDebugString = locationDebugString;
1745         getDexoptStatusResult.artifactsLocation = location;
1746         return getDexoptStatusResult;
1747     }
1748 
createSecondaryDexInfo(String dexPath, UserHandle userHandle)1749     private CheckedSecondaryDexInfo createSecondaryDexInfo(String dexPath, UserHandle userHandle)
1750             throws Exception {
1751         var dexInfo = mock(CheckedSecondaryDexInfo.class);
1752         lenient().when(dexInfo.dexPath()).thenReturn(dexPath);
1753         lenient().when(dexInfo.abiNames()).thenReturn(Set.of("arm64-v8a"));
1754         lenient().when(dexInfo.classLoaderContext()).thenReturn("CLC");
1755         lenient().when(dexInfo.userHandle()).thenReturn(userHandle);
1756         return dexInfo;
1757     }
1758 
simulateStorageLow()1759     private void simulateStorageLow() throws Exception {
1760         lenient()
1761                 .when(mStorageManager.getAllocatableBytes(any()))
1762                 .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES - 1);
1763     }
1764 
simulateStorageNotLow()1765     private void simulateStorageNotLow() throws Exception {
1766         lenient()
1767                 .when(mStorageManager.getAllocatableBytes(any()))
1768                 .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES);
1769     }
1770 
createProcessInfo( int pid, String processName, int uid, String[] pkgList, int importance)1771     private RunningAppProcessInfo createProcessInfo(
1772             int pid, String processName, int uid, String[] pkgList, int importance) {
1773         var info = new RunningAppProcessInfo(processName, pid, pkgList);
1774         info.uid = uid;
1775         info.importance = importance;
1776         return info;
1777     }
1778 }
1779