Merge "Move disk reads to background"

This commit is contained in:
Rajeev Kumar
2021-03-23 01:54:30 +00:00
committed by Gerrit Code Review
3 changed files with 63 additions and 27 deletions

View File

@@ -69,6 +69,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -101,6 +102,7 @@ public final class AppHibernationService extends SystemService {
private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>(); private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>();
private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore; private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore;
private final Injector mInjector; private final Injector mInjector;
private final Executor mBackgroundExecutor;
@VisibleForTesting @VisibleForTesting
boolean mIsServiceEnabled; boolean mIsServiceEnabled;
@@ -126,6 +128,7 @@ public final class AppHibernationService extends SystemService {
mIActivityManager = injector.getActivityManager(); mIActivityManager = injector.getActivityManager();
mUserManager = injector.getUserManager(); mUserManager = injector.getUserManager();
mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
mBackgroundExecutor = injector.getBackgroundExecutor();
mInjector = injector; mInjector = injector;
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
@@ -147,11 +150,13 @@ public final class AppHibernationService extends SystemService {
@Override @Override
public void onBootPhase(int phase) { public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) { if (phase == PHASE_BOOT_COMPLETED) {
mBackgroundExecutor.execute(() -> {
List<GlobalLevelState> states = List<GlobalLevelState> states =
mGlobalLevelHibernationDiskStore.readHibernationStates(); mGlobalLevelHibernationDiskStore.readHibernationStates();
synchronized (mLock) { synchronized (mLock) {
initializeGlobalHibernationStates(states); initializeGlobalHibernationStates(states);
} }
});
} }
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mIsServiceEnabled = isAppHibernationEnabled(); mIsServiceEnabled = isAppHibernationEnabled();
@@ -170,16 +175,15 @@ public final class AppHibernationService extends SystemService {
* @return true if package is hibernating for the user * @return true if package is hibernating for the user
*/ */
boolean isHibernatingForUser(String packageName, int userId) { boolean isHibernatingForUser(String packageName, int userId) {
if (!checkHibernationEnabled("isHibernatingForUser")) { String methodName = "isHibernatingForUser";
if (!checkHibernationEnabled(methodName)) {
return false; return false;
} }
getContext().enforceCallingOrSelfPermission( getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION, android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission."); "Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, "isHibernating"); userId = handleIncomingUser(userId, methodName);
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { if (!checkUserStatesExist(userId, methodName)) {
Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user "
+ userId);
return false; return false;
} }
synchronized (mLock) { synchronized (mLock) {
@@ -225,16 +229,15 @@ public final class AppHibernationService extends SystemService {
* @param isHibernating new hibernation state * @param isHibernating new hibernation state
*/ */
void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
if (!checkHibernationEnabled("setHibernatingForUser")) { String methodName = "setHibernatingForUser";
if (!checkHibernationEnabled(methodName)) {
return; return;
} }
getContext().enforceCallingOrSelfPermission( getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION, android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission."); "Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, "setHibernating"); userId = handleIncomingUser(userId, methodName);
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { if (!checkUserStatesExist(userId, methodName)) {
Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user "
+ userId);
return; return;
} }
synchronized (mLock) { synchronized (mLock) {
@@ -298,16 +301,15 @@ public final class AppHibernationService extends SystemService {
*/ */
@NonNull List<String> getHibernatingPackagesForUser(int userId) { @NonNull List<String> getHibernatingPackagesForUser(int userId) {
ArrayList<String> hibernatingPackages = new ArrayList<>(); ArrayList<String> hibernatingPackages = new ArrayList<>();
if (!checkHibernationEnabled("getHibernatingPackagesForUser")) { String methodName = "getHibernatingPackagesForUser";
if (!checkHibernationEnabled(methodName)) {
return hibernatingPackages; return hibernatingPackages;
} }
getContext().enforceCallingOrSelfPermission( getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION, android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission."); "Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, "getHibernatingPackagesForUser"); userId = handleIncomingUser(userId, methodName);
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { if (!checkUserStatesExist(userId, methodName)) {
Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user "
+ userId);
return hibernatingPackages; return hibernatingPackages;
} }
synchronized (mLock) { synchronized (mLock) {
@@ -477,11 +479,16 @@ public final class AppHibernationService extends SystemService {
HibernationStateDiskStore<UserLevelState> diskStore = HibernationStateDiskStore<UserLevelState> diskStore =
mInjector.getUserLevelDiskStore(userId); mInjector.getUserLevelDiskStore(userId);
mUserDiskStores.put(userId, diskStore); mUserDiskStores.put(userId, diskStore);
mBackgroundExecutor.execute(() -> {
List<UserLevelState> storedStates = diskStore.readHibernationStates(); List<UserLevelState> storedStates = diskStore.readHibernationStates();
synchronized (mLock) { synchronized (mLock) {
// Ensure user hasn't stopped in the time to execute.
if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
initializeUserHibernationStates(userId, storedStates); initializeUserHibernationStates(userId, storedStates);
} }
} }
});
}
@Override @Override
public void onUserStopping(@NonNull TargetUser user) { public void onUserStopping(@NonNull TargetUser user) {
@@ -550,6 +557,20 @@ public final class AppHibernationService extends SystemService {
} }
} }
private boolean checkUserStatesExist(int userId, String methodName) {
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
Slog.e(TAG, String.format(
"Attempt to call %s on stopped or nonexistent user %d", methodName, userId));
return false;
}
if (!mUserStates.contains(userId)) {
Slog.w(TAG, String.format(
"Attempt to call %s before states have been read from disk", methodName));
return false;
}
return true;
}
private boolean checkHibernationEnabled(String methodName) { private boolean checkHibernationEnabled(String methodName) {
if (!mIsServiceEnabled) { if (!mIsServiceEnabled) {
Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName)); Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName));
@@ -720,6 +741,8 @@ public final class AppHibernationService extends SystemService {
UserManager getUserManager(); UserManager getUserManager();
Executor getBackgroundExecutor();
HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore(); HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId); HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
@@ -757,6 +780,11 @@ public final class AppHibernationService extends SystemService {
return mContext.getSystemService(UserManager.class); return mContext.getSystemService(UserManager.class);
} }
@Override
public Executor getBackgroundExecutor() {
return mScheduledExecutorService;
}
@Override @Override
public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME);

View File

@@ -109,6 +109,7 @@ class HibernationStateDiskStore<T> {
* @return the parsed list of hibernation states, null if file does not exist * @return the parsed list of hibernation states, null if file does not exist
*/ */
@Nullable @Nullable
@WorkerThread
List<T> readHibernationStates() { List<T> readHibernationStates() {
synchronized (this) { synchronized (this) {
if (!mHibernationFile.exists()) { if (!mHibernationFile.exists()) {

View File

@@ -58,6 +58,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor;
/** /**
* Tests for {@link com.android.server.apphibernation.AppHibernationService} * Tests for {@link com.android.server.apphibernation.AppHibernationService}
@@ -116,8 +117,8 @@ public final class AppHibernationServiceTest {
mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
UserInfo userInfo = addUser(USER_ID_1); UserInfo userInfo = addUser(USER_ID_1);
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1);
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
mAppHibernationService.mIsServiceEnabled = true; mAppHibernationService.mIsServiceEnabled = true;
} }
@@ -150,8 +151,8 @@ public final class AppHibernationServiceTest {
throws RemoteException { throws RemoteException {
// WHEN a new user is added and a package from the user is hibernated // WHEN a new user is added and a package from the user is hibernated
UserInfo user2 = addUser(USER_ID_2); UserInfo user2 = addUser(USER_ID_2);
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
// THEN the new user's package is hibernated // THEN the new user's package is hibernated
@@ -188,8 +189,8 @@ public final class AppHibernationServiceTest {
// GIVEN an unlocked user with all packages installed // GIVEN an unlocked user with all packages installed
UserInfo userInfo = UserInfo userInfo =
addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3}); addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
// WHEN packages are hibernated for the user // WHEN packages are hibernated for the user
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
@@ -258,6 +259,12 @@ public final class AppHibernationServiceTest {
return mUserManager; return mUserManager;
} }
@Override
public Executor getBackgroundExecutor() {
// Just execute immediately in tests.
return r -> r.run();
}
@Override @Override
public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
return Mockito.mock(HibernationStateDiskStore.class); return Mockito.mock(HibernationStateDiskStore.class);