Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb66a82f authored by Kevin Han's avatar Kevin Han
Browse files

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
parent d560996d
Loading
Loading
Loading
Loading
+24 −4
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
@@ -418,19 +419,29 @@ public final class AppHibernationService extends SystemService {
        }
        }


        if (diskStates != null) {
        if (diskStates != null) {
            Set<String> installedPackages = new ArraySet<>();
            Map<String, PackageInfo> installedPackages = new ArrayMap<>();
            for (int i = 0, size = packages.size(); i < size; i++) {
            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++) {
            for (int i = 0, size = diskStates.size(); i < size; i++) {
                String packageName = diskStates.get(i).packageName;
                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(
                    Slog.w(TAG, String.format(
                            "No hibernation state associated with package %s user %d. Maybe"
                            "No hibernation state associated with package %s user %d. Maybe"
                                    + "the package was uninstalled? ", packageName, userId));
                                    + "the package was uninstalled? ", packageName, userId));
                    continue;
                    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);
        mUserStates.put(userId, userLevelStates);
@@ -487,6 +498,15 @@ public final class AppHibernationService extends SystemService {
                // Ensure user hasn't stopped in the time to execute.
                // Ensure user hasn't stopped in the time to execute.
                if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
                if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
                    initializeUserHibernationStates(userId, storedStates);
                    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);
                        }
                    }
                }
                }
            }
            }
        });
        });
+76 −4
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.apphibernation;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_ANY_USER;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
@@ -35,6 +36,7 @@ import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal;
@@ -89,7 +91,7 @@ public final class AppHibernationServiceTest {
    @Mock
    @Mock
    private UserManager mUserManager;
    private UserManager mUserManager;
    @Mock
    @Mock
    private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore;
    private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore;
    @Captor
    @Captor
    private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
    private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;


@@ -207,6 +209,61 @@ public final class AppHibernationServiceTest {
        assertTrue(hibernatingPackages.contains(PACKAGE_NAME_2));
        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.
     * Add a mock user with one package.
     */
     */
@@ -218,12 +275,19 @@ public final class AppHibernationServiceTest {
     * Add a mock user with the packages specified.
     * Add a mock user with the packages specified.
     */
     */
    private UserInfo addUser(int userId, String[] packageNames) throws RemoteException {
    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<>();
        List<PackageInfo> userPackages = new ArrayList<>();
        for (String pkgName : packageNames) {
        for (String pkgName : packageNames) {
            userPackages.add(makePackageInfo(pkgName));
            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)
        doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager)
                .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId));
                .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId));
        return userInfo;
        return userInfo;
@@ -232,9 +296,17 @@ public final class AppHibernationServiceTest {
    private static PackageInfo makePackageInfo(String packageName) {
    private static PackageInfo makePackageInfo(String packageName) {
        PackageInfo pkg = new PackageInfo();
        PackageInfo pkg = new PackageInfo();
        pkg.packageName = packageName;
        pkg.packageName = packageName;
        pkg.applicationInfo = new ApplicationInfo();
        return pkg;
        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 class MockInjector implements AppHibernationService.Injector {
        private final Context mContext;
        private final Context mContext;


@@ -280,7 +352,7 @@ public final class AppHibernationServiceTest {


        @Override
        @Override
        public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
        public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
            return mock(HibernationStateDiskStore.class);
            return mUserLevelDiskStore;
        }
        }


        @Override
        @Override