Merge "Unhibernate apps on boot that are not force-stopped" into sc-dev
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index aa51800..881a047 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.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 @@
         }
 
         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 @@
                 // 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 f280aea..6d76ad8 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 @@
 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.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 @@
     @Mock
     private UserManager mUserManager;
     @Mock
-    private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore;
+    private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore;
     @Captor
     private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
 
@@ -207,6 +209,61 @@
         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 @@
      * 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 @@
     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 @@
 
         @Override
         public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
-            return mock(HibernationStateDiskStore.class);
+            return mUserLevelDiskStore;
         }
 
         @Override