| /* |
| * Copyright (C) 2021 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.apphibernation; |
| |
| import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; |
| import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED; |
| import static android.app.usage.UsageEvents.Event.USER_INTERACTION; |
| 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; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.intThat; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| |
| import android.app.IActivityManager; |
| import android.app.usage.UsageEvents.Event; |
| import android.app.usage.UsageStatsManagerInternal; |
| import android.app.usage.UsageStatsManagerInternal.UsageEventListener; |
| 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; |
| import android.content.pm.ParceledListSlice; |
| import android.content.pm.UserInfo; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import android.os.UserManager; |
| import android.platform.test.annotations.Presubmit; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.server.LocalServices; |
| import com.android.server.SystemService; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * Tests for {@link com.android.server.apphibernation.AppHibernationService} |
| */ |
| @SmallTest |
| @Presubmit |
| public final class AppHibernationServiceTest { |
| private static final String PACKAGE_SCHEME = "package"; |
| private static final String PACKAGE_NAME_1 = "package1"; |
| private static final String PACKAGE_NAME_2 = "package2"; |
| private static final String PACKAGE_NAME_3 = "package3"; |
| private static final int USER_ID_1 = 1; |
| private static final int USER_ID_2 = 2; |
| |
| private final List<UserInfo> mUserInfos = new ArrayList<>(); |
| |
| private AppHibernationService mAppHibernationService; |
| private BroadcastReceiver mBroadcastReceiver; |
| private UsageEventListener mUsageEventListener; |
| |
| @Mock |
| private Context mContext; |
| @Mock |
| private IPackageManager mIPackageManager; |
| @Mock |
| private PackageManagerInternal mPackageManagerInternal; |
| @Mock |
| private IActivityManager mIActivityManager; |
| @Mock |
| private UserManager mUserManager; |
| @Mock |
| private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore; |
| @Mock |
| private UsageStatsManagerInternal mUsageStatsManagerInternal; |
| @Mock |
| private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore; |
| @Captor |
| private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor; |
| @Captor |
| private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor; |
| |
| @Before |
| public void setUp() throws RemoteException { |
| // Share class loader to allow access to package-private classes |
| System.setProperty("dexmaker.share_classloader", "true"); |
| MockitoAnnotations.initMocks(this); |
| doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); |
| |
| LocalServices.removeServiceForTest(AppHibernationManagerInternal.class); |
| mAppHibernationService = new AppHibernationService(new MockInjector(mContext)); |
| |
| verify(mContext).registerReceiver(mReceiverCaptor.capture(), any()); |
| mBroadcastReceiver = mReceiverCaptor.getValue(); |
| verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture()); |
| mUsageEventListener = mUsageEventListenerCaptor.getValue(); |
| |
| doReturn(mUserInfos).when(mUserManager).getUsers(); |
| |
| doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), |
| anyInt(), anyBoolean(), anyBoolean(), any(), any()); |
| |
| List<PackageInfo> packages = new ArrayList<>(); |
| packages.add(makePackageInfo(PACKAGE_NAME_1)); |
| packages.add(makePackageInfo(PACKAGE_NAME_2)); |
| packages.add(makePackageInfo(PACKAGE_NAME_3)); |
| doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( |
| intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); |
| mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); |
| |
| UserInfo userInfo = addUser(USER_ID_1); |
| doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); |
| mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); |
| |
| mAppHibernationService.sIsServiceEnabled = true; |
| } |
| |
| @Test |
| public void testSetHibernatingForUser_packageIsHibernating() throws Exception { |
| // WHEN we hibernate a package for a user |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); |
| |
| // THEN the package is marked hibernating for the user |
| assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); |
| verify(mIActivityManager).forceStopPackage(PACKAGE_NAME_1, USER_ID_1); |
| verify(mIPackageManager).deleteApplicationCacheFilesAsUser( |
| eq(PACKAGE_NAME_1), eq(USER_ID_1), any()); |
| } |
| |
| @Test |
| public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() { |
| // WHEN a new package is added and it is hibernated |
| Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED, |
| Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); |
| intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1); |
| mBroadcastReceiver.onReceive(mContext, intent); |
| |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_1, true); |
| |
| // THEN the new package is hibernated |
| assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_2, USER_ID_1)); |
| } |
| |
| @Test |
| public void testSetHibernatingForUser_newUserUnlocked_packageIsHibernating() |
| throws RemoteException { |
| // WHEN a new user is added and a package from the user is hibernated |
| UserInfo user2 = addUser(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); |
| |
| // THEN the new user's package is hibernated |
| assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_2)); |
| } |
| |
| @Test |
| public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() { |
| // GIVEN a package is currently hibernated |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); |
| |
| // WHEN the package is removed but marked as replacing |
| Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED, |
| Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); |
| intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1); |
| intent.putExtra(Intent.EXTRA_REPLACING, true); |
| mBroadcastReceiver.onReceive(mContext, intent); |
| |
| // THEN the package is still hibernating |
| assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); |
| } |
| |
| @Test |
| public void testSetHibernatingGlobally_packageIsHibernatingGlobally() throws RemoteException { |
| // WHEN we hibernate a package |
| mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); |
| |
| // THEN the package is marked hibernating for the user |
| assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); |
| verify(mPackageManagerInternal).deleteOatArtifactsOfPackage(PACKAGE_NAME_1); |
| } |
| |
| @Test |
| public void testGetHibernatingPackagesForUser_returnsCorrectPackages() throws RemoteException { |
| // GIVEN an unlocked user with all packages installed |
| UserInfo userInfo = |
| addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3}); |
| doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); |
| mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); |
| |
| // WHEN packages are hibernated for the user |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true); |
| |
| // THEN the hibernating packages returned matches |
| List<String> hibernatingPackages = |
| mAppHibernationService.getHibernatingPackagesForUser(USER_ID_2); |
| assertEquals(2, hibernatingPackages.size()); |
| assertTrue(hibernatingPackages.contains(PACKAGE_NAME_1)); |
| 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)); |
| } |
| |
| @Test |
| public void testUnhibernatingPackageForUserSendsBootCompleteBroadcast() |
| throws RemoteException { |
| // GIVEN a hibernating package for a user |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); |
| |
| // WHEN we unhibernate the package |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, false); |
| |
| // THEN we send the boot complete broadcasts |
| ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); |
| verify(mIActivityManager, times(2)).broadcastIntentWithFeature(any(), any(), |
| intentArgumentCaptor.capture(), any(), any(), anyInt(), any(), any(), any(), any(), |
| any(), anyInt(), any(), anyBoolean(), anyBoolean(), eq(USER_ID_1)); |
| List<Intent> capturedIntents = intentArgumentCaptor.getAllValues(); |
| assertEquals(capturedIntents.get(0).getAction(), Intent.ACTION_LOCKED_BOOT_COMPLETED); |
| assertEquals(capturedIntents.get(1).getAction(), Intent.ACTION_BOOT_COMPLETED); |
| } |
| |
| @Test |
| public void testHibernatingPackageIsUnhibernatedForUserWhenUserInteracted() { |
| // GIVEN a package that is currently hibernated for a user |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); |
| |
| // WHEN the package is interacted with by user |
| generateUsageEvent(USER_INTERACTION); |
| |
| // THEN the package is not hibernating anymore |
| assertFalse(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); |
| } |
| |
| @Test |
| public void testHibernatingPackageIsUnhibernatedForUserWhenActivityResumed() { |
| // GIVEN a package that is currently hibernated for a user |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); |
| |
| // WHEN the package has activity resumed |
| generateUsageEvent(ACTIVITY_RESUMED); |
| |
| // THEN the package is not hibernating anymore |
| assertFalse(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); |
| } |
| |
| @Test |
| public void testHibernatingPackageIsUnhibernatedForUserWhenComponentUsed() { |
| // GIVEN a package that is currently hibernated for a user |
| mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); |
| |
| // WHEN a package component is used |
| generateUsageEvent(APP_COMPONENT_USED); |
| |
| // THEN the package is not hibernating anymore |
| assertFalse(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); |
| } |
| |
| @Test |
| public void testHibernatingPackageIsUnhibernatedGloballyWhenUserInteracted() { |
| // GIVEN a package that is currently hibernated globally |
| mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); |
| |
| // WHEN the user interacts with the package |
| generateUsageEvent(USER_INTERACTION); |
| |
| // THEN the package is not hibernating globally anymore |
| assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); |
| } |
| |
| @Test |
| public void testHibernatingPackageIsUnhibernatedGloballyWhenActivityResumed() { |
| // GIVEN a package that is currently hibernated globally |
| mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); |
| |
| // WHEN activity in package resumed |
| generateUsageEvent(ACTIVITY_RESUMED); |
| |
| // THEN the package is not hibernating globally anymore |
| assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); |
| } |
| |
| @Test |
| public void testHibernatingPackageIsUnhibernatedGloballyWhenComponentUsed() { |
| // GIVEN a package that is currently hibernated globally |
| mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); |
| |
| // WHEN a package component is used |
| generateUsageEvent(APP_COMPONENT_USED); |
| |
| // THEN the package is not hibernating globally anymore |
| assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); |
| } |
| |
| /** |
| * Mock a usage event occurring. |
| * |
| * @param usageEventId id of a usage event |
| */ |
| private void generateUsageEvent(int usageEventId) { |
| Event event = new Event(usageEventId, 0 /* timestamp */); |
| event.mPackage = PACKAGE_NAME_1; |
| mUsageEventListener.onUsageEvent(USER_ID_1, event); |
| } |
| |
| /** |
| * Add a mock user with one package. |
| */ |
| private UserInfo addUser(int userId) throws RemoteException { |
| return addUser(userId, new String[]{PACKAGE_NAME_1}); |
| } |
| |
| /** |
| * Add a mock user with the packages specified. |
| */ |
| private UserInfo addUser(int userId, String[] packageNames) throws RemoteException { |
| 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; |
| } |
| |
| 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; |
| |
| MockInjector(Context context) { |
| mContext = context; |
| } |
| |
| @Override |
| public IActivityManager getActivityManager() { |
| return mIActivityManager; |
| } |
| |
| @Override |
| public Context getContext() { |
| return mContext; |
| } |
| |
| @Override |
| public IPackageManager getPackageManager() { |
| return mIPackageManager; |
| } |
| |
| @Override |
| public PackageManagerInternal getPackageManagerInternal() { |
| return mPackageManagerInternal; |
| } |
| |
| @Override |
| public UserManager getUserManager() { |
| return mUserManager; |
| } |
| |
| @Override |
| public UsageStatsManagerInternal getUsageStatsManagerInternal() { |
| return mUsageStatsManagerInternal; |
| } |
| |
| @Override |
| public Executor getBackgroundExecutor() { |
| // Just execute immediately in tests. |
| return r -> r.run(); |
| } |
| |
| @Override |
| public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { |
| return mock(HibernationStateDiskStore.class); |
| } |
| |
| @Override |
| public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { |
| return mUserLevelDiskStore; |
| } |
| |
| @Override |
| public boolean isOatArtifactDeletionEnabled() { |
| return true; |
| } |
| } |
| } |