Implement app downgrading.
Also:
- Filter the default package list by last active time and sort it in
descending order.
Bug: 255565888
Test: atest ArtServiceTests
Test: -
1. Fill the storage space by `fallocate`.
2. adb shell setprop pm.dexopt.downgrade_after_inactive_days 1
3. adb shell pm art bg-dexopt-job
4. See some apps being downgraded and the other apps being optimized.
Ignore-AOSP-First: ART Services.
Change-Id: I8594f67aa10da5bc907c92bb7b0d1aaf095d3c33
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index b02bfdc..bf77989 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -42,8 +42,12 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalManagerRegistry;
@@ -62,16 +66,22 @@
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* This class provides a system API for functionality provided by the ART module.
@@ -89,6 +99,7 @@
private static final String TAG = "ArtService";
private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
"BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
+ private static final long DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES = 500_000_000;
@NonNull private final Injector mInjector;
@@ -314,6 +325,17 @@
* When this operation ends (either completed or cancelled), callbacks added by {@link
* #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} are called.
*
+ * If the storage is nearly low, and {@code reason} is {@link ReasonMapping#REASON_BG_DEXOPT},
+ * it may also downgrade some inactive packages to a less optimized compiler filter, specified
+ * by the system property {@code pm.dexopt.inactive} (typically "verify"), to free up some
+ * space. This feature is only enabled when the system property {@code
+ * pm.dexopt.downgrade_after_inactive_days} is set. The space threshold to trigger this feature
+ * is the Storage Manager's low space threshold plus {@link
+ * #DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES}. The concurrency can be configured by system property
+ * {@code pm.dexopt.inactive.concurrency}. The packages in the list provided by
+ * {@link OptimizePackagesCallback} for {@link ReasonMapping#REASON_BG_DEXOPT} are never
+ * downgraded.
+ *
* @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
* @param reason determines the default list of packages and options
* @param cancellationSignal provides the ability to cancel this operation
@@ -330,7 +352,7 @@
public OptimizeResult optimizePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
@NonNull @BatchOptimizeReason String reason,
@NonNull CancellationSignal cancellationSignal,
- @Nullable @CallbackExecutor Executor processCallbackExecutor,
+ @Nullable @CallbackExecutor Executor progressCallbackExecutor,
@Nullable Consumer<OperationProgress> progressCallback) {
List<String> defaultPackages =
Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
@@ -350,9 +372,15 @@
ExecutorService dexoptExecutor =
Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason));
try {
+ if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
+ maybeDowngradePackages(snapshot,
+ new HashSet<>(params.getPackages()) /* excludedPackages */,
+ cancellationSignal, dexoptExecutor);
+ }
+ Log.i(TAG, "Optimizing packages");
return mInjector.getDexOptHelper().dexopt(snapshot, params.getPackages(),
params.getOptimizeParams(), cancellationSignal, dexoptExecutor,
- processCallbackExecutor, progressCallback);
+ progressCallbackExecutor, progressCallback);
} finally {
dexoptExecutor.shutdown();
}
@@ -623,37 +651,84 @@
return mInjector.getBackgroundDexOptJob();
}
+ private void maybeDowngradePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull Set<String> excludedPackages, @NonNull CancellationSignal cancellationSignal,
+ @NonNull Executor executor) {
+ if (shouldDowngrade()) {
+ List<String> packages = getDefaultPackages(snapshot, ReasonMapping.REASON_INACTIVE)
+ .stream()
+ .filter(pkg -> !excludedPackages.contains(pkg))
+ .collect(Collectors.toList());
+ if (!packages.isEmpty()) {
+ Log.i(TAG, "Storage is low. Downgrading inactive packages");
+ mInjector.getDexOptHelper().dexopt(snapshot, packages,
+ new OptimizeParams.Builder(ReasonMapping.REASON_INACTIVE).build(),
+ cancellationSignal, executor, null /* processCallbackExecutor */,
+ null /* progressCallback */);
+ } else {
+ Log.i(TAG,
+ "Storage is low, but downgrading is disabled or there's nothing to "
+ + "downgrade");
+ }
+ }
+ }
+
+ private boolean shouldDowngrade() {
+ try {
+ return mInjector.getStorageManager().getAllocatableBytes(StorageManager.UUID_DEFAULT)
+ < DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
+ return false;
+ }
+ }
+
+ /** Returns the list of packages to process for the given reason. */
@NonNull
- private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
- @NonNull @BatchOptimizeReason String reason) {
- final List<String> packages;
+ private List<String> getDefaultPackages(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String reason) {
+ // Filter out hibernating packages even if the reason is REASON_INACTIVE. This is because
+ // artifacts for hibernating packages are already deleted.
+ Stream<PackageState> packages = snapshot.getPackageStates().values().stream().filter(
+ pkgState
+ -> Utils.canOptimizePackage(pkgState, mInjector.getAppHibernationManager()));
switch (reason) {
case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
- packages =
- snapshot.getPackageStates()
- .values()
- .stream()
- .filter(pkgState
- -> mInjector.isSystemUiPackage(pkgState.getPackageName()))
- .filter(pkgState
- -> Utils.canOptimizePackage(
- pkgState, mInjector.getAppHibernationManager()))
- .map(PackageState::getPackageName)
- .collect(Collectors.toList());
+ packages = packages.filter(
+ pkgState -> mInjector.isSystemUiPackage(pkgState.getPackageName()));
+ break;
+ case ReasonMapping.REASON_INACTIVE:
+ packages = filterAndSortByLastActiveTime(
+ packages, false /* keepRecent */, false /* descending */);
break;
default:
- // TODO(b/258818709): Filter packages by last active time.
- packages = snapshot.getPackageStates()
- .values()
- .stream()
- .filter(pkgState
- -> Utils.canOptimizePackage(
- pkgState, mInjector.getAppHibernationManager()))
- .map(PackageState::getPackageName)
- .collect(Collectors.toList());
- break;
+ // Actually, the sorting is only needed for background dexopt, but we do it for all
+ // cases for simplicity.
+ packages = filterAndSortByLastActiveTime(
+ packages, true /* keepRecent */, true /* descending */);
}
- return packages;
+ return packages.map(PackageState::getPackageName).collect(Collectors.toList());
+ }
+
+ @NonNull
+ private Stream<PackageState> filterAndSortByLastActiveTime(
+ @NonNull Stream<PackageState> packages, boolean keepRecent, boolean descending) {
+ // "pm.dexopt.downgrade_after_inactive_days" is repurposed to also determine whether to
+ // optimize a package.
+ long inactiveMs = TimeUnit.DAYS.toMillis(SystemProperties.getInt(
+ "pm.dexopt.downgrade_after_inactive_days", Integer.MAX_VALUE /* def */));
+ long currentTimeMs = mInjector.getCurrentTimeMillis();
+ long thresholdTimeMs = currentTimeMs - inactiveMs;
+ return packages
+ .map(pkgState
+ -> Pair.create(pkgState,
+ Utils.getPackageLastActiveTime(pkgState,
+ mInjector.getDexUseManager(), mInjector.getUserManager())))
+ .filter(keepRecent ? (pair -> pair.second > thresholdTimeMs)
+ : (pair -> pair.second <= thresholdTimeMs))
+ .sorted(descending ? Comparator.comparingLong(pair -> - pair.second)
+ : Comparator.comparingLong(pair -> pair.second))
+ .map(pair -> pair.first);
}
@NonNull
@@ -787,6 +862,7 @@
getAppHibernationManager();
getUserManager();
getDexUseManager();
+ getStorageManager();
ArtModuleServiceInitializer.getArtModuleServiceManager();
} else {
mPackageManagerLocal = null;
@@ -845,5 +921,14 @@
public boolean isSystemUiPackage(@NonNull String packageName) {
return packageName.equals(mContext.getString(R.string.config_systemUi));
}
+
+ public long getCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ @NonNull
+ public StorageManager getStorageManager() {
+ return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java b/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java
index 9c3a3c7..f56375b 100644
--- a/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java
@@ -196,7 +196,6 @@
private CompletedResult run(@NonNull CancellationSignal cancellationSignal) {
// TODO(b/254013427): Cleanup dex use info.
// TODO(b/254013425): Cleanup unused secondary dex file artifacts.
- // TODO(b/255565888): Downgrade inactive apps.
long startTimeMs = SystemClock.uptimeMillis();
OptimizeResult dexoptResult;
try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
diff --git a/libartservice/service/java/com/android/server/art/DexOptimizer.java b/libartservice/service/java/com/android/server/art/DexOptimizer.java
index c9b5f8d..6f3f7f1 100644
--- a/libartservice/service/java/com/android/server/art/DexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/DexOptimizer.java
@@ -652,6 +652,12 @@
public Injector(@NonNull Context context) {
mContext = context;
+
+ // Call the getters for various dependencies, to ensure correct initialization order.
+ getUserManager();
+ getDexUseManager();
+ getStorageManager();
+ ArtModuleServiceInitializer.getArtModuleServiceManager();
}
public boolean isSystemUiPackage(@NonNull String packageName) {
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 7752ba7..64a33ab 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -21,6 +21,7 @@
import android.apphibernation.AppHibernationManager;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.SparseArray;
@@ -268,6 +269,21 @@
return true;
}
+ public static long getPackageLastActiveTime(@NonNull PackageState pkgState,
+ @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) {
+ long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName());
+ // The time where the last user installed the package the first time.
+ long lastFirstInstallTimeMs =
+ userManager.getUserHandles(true /* excludeDying */)
+ .stream()
+ .map(handle -> pkgState.getStateForUser(handle))
+ .map(com.android.server.art.wrapper.PackageUserState::new)
+ .map(userState -> userState.getFirstInstallTime())
+ .max(Long::compare)
+ .orElse(0l);
+ return Math.max(lastUsedAtMs, lastFirstInstallTimeMs);
+ }
+
@AutoValue
public abstract static class Abi {
static @NonNull Abi create(
diff --git a/libartservice/service/java/com/android/server/art/wrapper/PackageUserState.java b/libartservice/service/java/com/android/server/art/wrapper/PackageUserState.java
new file mode 100644
index 0000000..9b5f019
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/PackageUserState.java
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.wrapper;
+
+import android.annotation.NonNull;
+
+/** @hide */
+public class PackageUserState {
+ @NonNull private final com.android.server.pm.pkg.PackageUserState mPkgUserState;
+
+ public PackageUserState(@NonNull com.android.server.pm.pkg.PackageUserState pkgUserState) {
+ mPkgUserState = pkgUserState;
+ }
+
+ public long getFirstInstallTime() {
+ try {
+ return (long) mPkgUserState.getClass()
+ .getMethod("getFirstInstallTime")
+ .invoke(mPkgUserState);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/README.md b/libartservice/service/java/com/android/server/art/wrapper/README.md
index a18a08a..ec52551 100644
--- a/libartservice/service/java/com/android/server/art/wrapper/README.md
+++ b/libartservice/service/java/com/android/server/art/wrapper/README.md
@@ -6,3 +6,4 @@
The mappings are:
- `Environment`: `android.os.Environment`
- `PackageState`: `com.android.server.pm.pkg.PackageState`
+- `PackageUserState`: `com.android.server.pm.pkg.PackageUserState`
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index dea9fda..44b0498 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -31,7 +31,9 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@@ -49,6 +51,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import androidx.test.filters.SmallTest;
@@ -80,6 +83,7 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -89,6 +93,12 @@
private static final String PKG_NAME = "com.example.foo";
private static final String PKG_NAME_SYS_UI = "com.android.systemui";
private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
+ private static final int INACTIVE_DAYS = 1;
+ private static final long CURRENT_TIME_MS = 10000000000l;
+ private static final long RECENT_TIME_MS =
+ CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) + 1;
+ private static final long NOT_RECENT_TIME_MS =
+ CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) - 1;
@Rule
public StaticMockitoRule mockitoRule =
@@ -101,6 +111,8 @@
@Mock private DexOptHelper mDexOptHelper;
@Mock private AppHibernationManager mAppHibernationManager;
@Mock private UserManager mUserManager;
+ @Mock private DexUseManagerLocal mDexUseManager;
+ @Mock private StorageManager mStorageManager;
private PackageState mPkgState;
private AndroidPackage mPkg;
private Config mConfig;
@@ -130,6 +142,9 @@
lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
lenient().when(mInjector.isSystemUiPackage(PKG_NAME_SYS_UI)).thenReturn(true);
+ lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+ lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS);
+ lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile");
lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile");
@@ -137,6 +152,7 @@
lenient()
.when(SystemProperties.get(eq("pm.dexopt.boot-after-mainline-update")))
.thenReturn("verify");
+ lenient().when(SystemProperties.get(eq("pm.dexopt.inactive"))).thenReturn("verify");
lenient()
.when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
.thenReturn(3);
@@ -144,6 +160,17 @@
.when(SystemProperties.getInt(
eq("pm.dexopt.boot-after-mainline-update.concurrency"), anyInt()))
.thenReturn(3);
+ lenient()
+ .when(SystemProperties.getInt(eq("pm.dexopt.inactive.concurrency"), anyInt()))
+ .thenReturn(3);
+ lenient()
+ .when(SystemProperties.getInt(
+ eq("pm.dexopt.downgrade_after_inactive_days"), anyInt()))
+ .thenReturn(INACTIVE_DAYS);
+ lenient()
+ .when(SystemProperties.getLong(
+ eq("pm.dexopt.storage_threshold_above_low_bytes"), anyLong()))
+ .thenReturn(1000l);
// No ISA translation.
lenient()
@@ -161,6 +188,11 @@
.when(mUserManager.getUserHandles(anyBoolean()))
.thenReturn(List.of(UserHandle.of(0), UserHandle.of(1)));
+ // All packages are by default recently used.
+ lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(RECENT_TIME_MS);
+
+ lenient().when(mStorageManager.getAllocatableBytes(any())).thenReturn(1000l);
+
lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
List<PackageState> pkgStates = createPackageStates();
for (PackageState pkgState : pkgStates) {
@@ -345,23 +377,90 @@
@Test
public void testOptimizePackages() throws Exception {
- var result = mock(OptimizeResult.class);
+ var optimizeResult = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
+ when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_SYS_UI)).thenReturn(CURRENT_TIME_MS);
+ when(mStorageManager.getAllocatableBytes(any())).thenReturn(999l);
- // It should use the default package list and params.
- when(mDexOptHelper.dexopt(any(), inAnyOrder(PKG_NAME, PKG_NAME_SYS_UI), any(),
- same(cancellationSignal), any(), any(), any()))
- .thenReturn(result);
+ // It should use the default package list and params. The list is sorted by last active
+ // time in descending order.
+ doReturn(optimizeResult)
+ .when(mDexOptHelper)
+ .dexopt(any(), deepEq(List.of(PKG_NAME_SYS_UI, PKG_NAME)),
+ argThat(params -> params.getReason().equals("bg-dexopt")),
+ same(cancellationSignal), any(), any(), any());
assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal,
null /* processCallbackExecutor */, null /* processCallback */))
- .isSameInstanceAs(result);
+ .isSameInstanceAs(optimizeResult);
+
+ // Nothing to downgrade.
+ verify(mDexOptHelper, never())
+ .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+ any(), any(), any(), any());
+ }
+
+ @Test
+ public void testOptimizePackagesRecentlyInstalled() throws Exception {
+ // The package is recently installed but hasn't been used.
+ PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+ when(userState.getFirstInstallTime()).thenReturn(RECENT_TIME_MS);
+ when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(0l);
+ when(mStorageManager.getAllocatableBytes(any())).thenReturn(999l);
+
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ // PKG_NAME should be optimized.
+ doReturn(result)
+ .when(mDexOptHelper)
+ .dexopt(any(), inAnyOrder(PKG_NAME, PKG_NAME_SYS_UI),
+ argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+ any(), any());
+
+ mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal,
+ null /* processCallbackExecutor */, null /* processCallback */);
+
+ // PKG_NAME should not be downgraded.
+ verify(mDexOptHelper, never())
+ .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+ any(), any(), any(), any());
+ }
+
+ @Test
+ public void testOptimizePackagesInactive() throws Exception {
+ // PKG_NAME is neither recently installed nor recently used.
+ PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+ when(userState.getFirstInstallTime()).thenReturn(NOT_RECENT_TIME_MS);
+ when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(NOT_RECENT_TIME_MS);
+ when(mStorageManager.getAllocatableBytes(any())).thenReturn(999l);
+
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ // PKG_NAME should not be optimized.
+ doReturn(result)
+ .when(mDexOptHelper)
+ .dexopt(any(), deepEq(List.of(PKG_NAME_SYS_UI)),
+ argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+ any(), any());
+
+ // PKG_NAME should be downgraded.
+ doReturn(result)
+ .when(mDexOptHelper)
+ .dexopt(any(), deepEq(List.of(PKG_NAME)),
+ argThat(params -> params.getReason().equals("inactive")), any(), any(),
+ any(), any());
+
+ mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal,
+ null /* processCallbackExecutor */, null /* processCallback */);
}
@Test
public void testOptimizePackagesBootAfterMainlineUpdate() throws Exception {
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
+ lenient().when(mStorageManager.getAllocatableBytes(any())).thenReturn(999l);
// It should only optimize system UI.
when(mDexOptHelper.dexopt(
@@ -372,10 +471,21 @@
cancellationSignal, null /* processCallbackExecutor */,
null /* processCallback */))
.isSameInstanceAs(result);
+
+ // It should never downgrade apps, even if the storage is low.
+ verify(mDexOptHelper, never())
+ .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+ any(), any(), any(), any());
}
@Test
public void testOptimizePackagesOverride() throws Exception {
+ // PKG_NAME is neither recently installed nor recently used.
+ PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+ when(userState.getFirstInstallTime()).thenReturn(NOT_RECENT_TIME_MS);
+ when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(NOT_RECENT_TIME_MS);
+ when(mStorageManager.getAllocatableBytes(any())).thenReturn(999l);
+
var params = new OptimizeParams.Builder("bg-dexopt").build();
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
@@ -383,18 +493,23 @@
mArtManagerLocal.setOptimizePackagesCallback(
ForkJoinPool.commonPool(), (snapshot, reason, defaultPackages, builder) -> {
assertThat(reason).isEqualTo("bg-dexopt");
- assertThat(defaultPackages).containsExactly(PKG_NAME, PKG_NAME_SYS_UI);
+ assertThat(defaultPackages).containsExactly(PKG_NAME_SYS_UI);
builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
});
// It should use the overridden package list and params.
- when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
- same(cancellationSignal), any(), any(), any()))
- .thenReturn(result);
+ doReturn(result)
+ .when(mDexOptHelper)
+ .dexopt(any(), deepEq(List.of(PKG_NAME)), same(params), any(), any(), any(), any());
- assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal,
- null /* processCallbackExecutor */, null /* processCallback */))
- .isSameInstanceAs(result);
+ mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal,
+ null /* processCallbackExecutor */, null /* processCallback */);
+
+ // It should not downgrade PKG_NAME because it's in the overridden package list. It should
+ // not downgrade PKG_NAME_SYS_UI either because it's not an inactive package.
+ verify(mDexOptHelper, never())
+ .dexopt(any(), any(), argThat(params2 -> params2.getReason().equals("inactive")),
+ any(), any(), any(), any());
}
@Test
@@ -603,6 +718,8 @@
private PackageUserState createPackageUserState() {
PackageUserState pkgUserState = mock(PackageUserState.class);
lenient().when(pkgUserState.isInstalled()).thenReturn(true);
+ // All packages are by default pre-installed.
+ lenient().when(pkgUserState.getFirstInstallTime()).thenReturn(0l);
return pkgUserState;
}
@@ -624,8 +741,10 @@
lenient().when(pkgState.getAndroidPackage()).thenReturn(null);
}
- PackageUserState pkgUserState = createPackageUserState();
- lenient().when(pkgState.getStateForUser(any())).thenReturn(pkgUserState);
+ PackageUserState pkgUserState0 = createPackageUserState();
+ lenient().when(pkgState.getStateForUser(UserHandle.of(0))).thenReturn(pkgUserState0);
+ PackageUserState pkgUserState1 = createPackageUserState();
+ lenient().when(pkgState.getStateForUser(UserHandle.of(1))).thenReturn(pkgUserState1);
return pkgState;
}