diff options
| author | 2021-04-08 13:59:46 -0700 | |
|---|---|---|
| committer | 2021-04-20 21:45:53 -0700 | |
| commit | bb66a82f151b46a7dff0e9c8eff339b6602c79dd (patch) | |
| tree | 93fc82ff40f1ecea678748c80f8541e5aa85b1ea | |
| parent | d560996d0fc45d685f89d0e9deb0fd84f13c7509 (diff) | |
Unhibernate apps on boot that are not force-stopped
Hibernation disk state can be stale and lead to getting inconsistent
hibernation states with force-stop. We resolve this by checking the
hibernation state against force-stop and unhibernating if the app is not
force-stopped but hibernating.
Similarly, if we unlock a user and determine that a package is not
hibernating for that user but the package is globally hibernating, we
globally unhibernate the package.
Bug: 185511307
Test: atest AppHibernationServiceTest
Change-Id: I574c69c60cbe388d937a7f59695f757ed8d08f5f
2 files changed, 100 insertions, 8 deletions
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index fd829fae5759..53e6fca4e858 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -35,6 +35,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -418,19 +419,29 @@ public final class AppHibernationService extends SystemService { } if (diskStates != null) { - Set<String> installedPackages = new ArraySet<>(); + Map<String, PackageInfo> installedPackages = new ArrayMap<>(); for (int i = 0, size = packages.size(); i < size; i++) { - installedPackages.add(packages.get(i).packageName); + installedPackages.put(packages.get(i).packageName, packages.get(i)); } for (int i = 0, size = diskStates.size(); i < size; i++) { String packageName = diskStates.get(i).packageName; - if (!installedPackages.contains(packageName)) { + PackageInfo pkgInfo = installedPackages.get(packageName); + UserLevelState currentState = diskStates.get(i); + if (pkgInfo == null) { Slog.w(TAG, String.format( "No hibernation state associated with package %s user %d. Maybe" + "the package was uninstalled? ", packageName, userId)); continue; } - userLevelStates.put(packageName, diskStates.get(i)); + if (pkgInfo.applicationInfo != null + && (pkgInfo.applicationInfo.flags &= ApplicationInfo.FLAG_STOPPED) == 0 + && currentState.hibernated) { + // App is not stopped but is hibernated. Disk state is stale, so unhibernate + // the app. + currentState.hibernated = false; + currentState.lastUnhibernatedMs = System.currentTimeMillis(); + } + userLevelStates.put(packageName, currentState); } } mUserStates.put(userId, userLevelStates); @@ -487,6 +498,15 @@ public final class AppHibernationService extends SystemService { // Ensure user hasn't stopped in the time to execute. if (mUserManager.isUserUnlockingOrUnlocked(userId)) { initializeUserHibernationStates(userId, storedStates); + // Globally unhibernate a package if the unlocked user does not have it + // hibernated. + for (UserLevelState userState : mUserStates.get(userId).values()) { + String pkgName = userState.packageName; + GlobalLevelState globalState = mGlobalHibernationStates.get(pkgName); + if (globalState.hibernated && !userState.hibernated) { + setHibernatingGlobally(pkgName, false); + } + } } } }); diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index f280aea92851..6d76ad895d4a 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -19,6 +19,7 @@ package com.android.server.apphibernation; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.returnsArgAt; import static org.mockito.ArgumentMatchers.any; @@ -35,6 +36,7 @@ import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManagerInternal; @@ -89,7 +91,7 @@ public final class AppHibernationServiceTest { @Mock private UserManager mUserManager; @Mock - private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore; + private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore; @Captor private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor; @@ -207,6 +209,61 @@ public final class AppHibernationServiceTest { assertTrue(hibernatingPackages.contains(PACKAGE_NAME_2)); } + @Test + public void testUserLevelStatesInitializedFromDisk() throws RemoteException { + // GIVEN states stored on disk that match with package manager's force-stop states + List<UserLevelState> diskStates = new ArrayList<>(); + diskStates.add(makeUserLevelState(PACKAGE_NAME_1, false /* hibernated */)); + diskStates.add(makeUserLevelState(PACKAGE_NAME_2, true /* hibernated */)); + doReturn(diskStates).when(mUserLevelDiskStore).readHibernationStates(); + + List<PackageInfo> packageInfos = new ArrayList<>(); + packageInfos.add(makePackageInfo(PACKAGE_NAME_1)); + PackageInfo stoppedPkg = makePackageInfo(PACKAGE_NAME_2); + stoppedPkg.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED; + packageInfos.add(stoppedPkg); + + // WHEN a user is unlocked and the states are initialized + UserInfo user2 = addUser(USER_ID_2, packageInfos); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); + + // THEN the hibernation states are initialized to the disk states + assertFalse(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_2)); + assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_2, USER_ID_2)); + } + + @Test + public void testNonForceStoppedAppsNotHibernatedOnUnlock() throws RemoteException { + // GIVEN a package that is hibernated on disk but not force-stopped + List<UserLevelState> diskStates = new ArrayList<>(); + diskStates.add(makeUserLevelState(PACKAGE_NAME_1, true /* hibernated */)); + doReturn(diskStates).when(mUserLevelDiskStore).readHibernationStates(); + + // WHEN a user is unlocked and the states are initialized + UserInfo user2 = addUser(USER_ID_2, new String[]{PACKAGE_NAME_1}); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); + + // THEN the app is not hibernating for the user + assertFalse(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_2)); + } + + @Test + public void testUnhibernatedPackageForUserUnhibernatesPackageGloballyOnUnlock() + throws RemoteException { + // GIVEN a package that is globally hibernating + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); + + // WHEN a user is unlocked and the package is not hibernating for the user + UserInfo user2 = addUser(USER_ID_2); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); + + // THEN the package is no longer globally hibernating + assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); + } + /** * Add a mock user with one package. */ @@ -218,12 +275,19 @@ public final class AppHibernationServiceTest { * Add a mock user with the packages specified. */ private UserInfo addUser(int userId, String[] packageNames) throws RemoteException { - UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */); - mUserInfos.add(userInfo); List<PackageInfo> userPackages = new ArrayList<>(); for (String pkgName : packageNames) { userPackages.add(makePackageInfo(pkgName)); } + return addUser(userId, userPackages); + } + + /** + * Add a mock user with the package infos specified. + */ + private UserInfo addUser(int userId, List<PackageInfo> userPackages) throws RemoteException { + UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */); + mUserInfos.add(userInfo); doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId)); return userInfo; @@ -232,9 +296,17 @@ public final class AppHibernationServiceTest { private static PackageInfo makePackageInfo(String packageName) { PackageInfo pkg = new PackageInfo(); pkg.packageName = packageName; + pkg.applicationInfo = new ApplicationInfo(); return pkg; } + private static UserLevelState makeUserLevelState(String packageName, boolean hibernated) { + UserLevelState state = new UserLevelState(); + state.packageName = packageName; + state.hibernated = hibernated; + return state; + } + private class MockInjector implements AppHibernationService.Injector { private final Context mContext; @@ -280,7 +352,7 @@ public final class AppHibernationServiceTest { @Override public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { - return mock(HibernationStateDiskStore.class); + return mUserLevelDiskStore; } @Override |