diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index efb0f4a9343cc..d7f417079fcae 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -33,6 +33,9 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.IActivityManager; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.UsageEventListener; import android.apphibernation.IAppHibernationService; import android.content.BroadcastReceiver; import android.content.Context; @@ -148,8 +151,8 @@ public final class AppHibernationService extends SystemService { intentFilter.addAction(ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); userAllContext.registerReceiver(mBroadcastReceiver, intentFilter); - LocalServices.addService(AppHibernationManagerInternal.class, mLocalService); + mInjector.getUsageStatsManagerInternal().registerListener(mUsageEventListener); } @Override @@ -785,6 +788,20 @@ public final class AppHibernationService extends SystemService { } }; + private final UsageEventListener mUsageEventListener = (userId, event) -> { + if (!isAppHibernationEnabled()) { + return; + } + final int eventType = event.mEventType; + if (eventType == UsageEvents.Event.USER_INTERACTION + || eventType == UsageEvents.Event.ACTIVITY_RESUMED + || eventType == UsageEvents.Event.APP_COMPONENT_USED) { + final String pkgName = event.mPackage; + setHibernatingForUser(pkgName, userId, false); + setHibernatingGlobally(pkgName, false); + } + }; + /** * Whether app hibernation is enabled on this device. * @@ -817,6 +834,8 @@ public final class AppHibernationService extends SystemService { Executor getBackgroundExecutor(); + UsageStatsManagerInternal getUsageStatsManagerInternal(); + HibernationStateDiskStore getGlobalLevelDiskStore(); HibernationStateDiskStore getUserLevelDiskStore(int userId); @@ -866,6 +885,11 @@ public final class AppHibernationService extends SystemService { return mScheduledExecutorService; } + @Override + public UsageStatsManagerInternal getUsageStatsManagerInternal() { + return LocalServices.getService(UsageStatsManagerInternal.class); + } + @Override public HibernationStateDiskStore getGlobalLevelDiskStore() { File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); 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 4a6c9be58c11d..5c7a580a25936 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -16,6 +16,9 @@ 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; @@ -34,6 +37,9 @@ 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; @@ -81,6 +87,8 @@ public final class AppHibernationServiceTest { private AppHibernationService mAppHibernationService; private BroadcastReceiver mBroadcastReceiver; + private UsageEventListener mUsageEventListener; + @Mock private Context mContext; @Mock @@ -93,8 +101,14 @@ public final class AppHibernationServiceTest { private UserManager mUserManager; @Mock private HibernationStateDiskStore mUserLevelDiskStore; + @Mock + private UsageStatsManagerInternal mUsageStatsManagerInternal; + @Mock + private HibernationStateDiskStore mHibernationStateDiskStore; @Captor private ArgumentCaptor mReceiverCaptor; + @Captor + private ArgumentCaptor mUsageEventListenerCaptor; @Before public void setUp() throws RemoteException { @@ -108,6 +122,8 @@ public final class AppHibernationServiceTest { verify(mContext).registerReceiver(mReceiverCaptor.capture(), any()); mBroadcastReceiver = mReceiverCaptor.getValue(); + verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture()); + mUsageEventListener = mUsageEventListenerCaptor.getValue(); doReturn(mUserInfos).when(mUserManager).getUsers(); @@ -284,6 +300,89 @@ public final class AppHibernationServiceTest { 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. */ @@ -359,6 +458,11 @@ public final class AppHibernationServiceTest { return mUserManager; } + @Override + public UsageStatsManagerInternal getUsageStatsManagerInternal() { + return mUsageStatsManagerInternal; + } + @Override public Executor getBackgroundExecutor() { // Just execute immediately in tests.