blob: 70853b6881d91253034ad4f51e2f72b07e74b6f5 [file] [log] [blame]
/*
* 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;
}
}
}