Loading services/core/java/com/android/server/apphibernation/AppHibernationService.java +30 −4 Original line number Diff line number Diff line Loading @@ -35,6 +35,8 @@ import android.app.ActivityThread; import android.app.IActivityManager; import android.app.StatsManager; import android.app.StatsManager.StatsPullAtomCallback; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; Loading Loading @@ -78,6 +80,7 @@ import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; Loading Loading @@ -111,6 +114,7 @@ public final class AppHibernationService extends SystemService { private final PackageManagerInternal mPackageManagerInternal; private final IActivityManager mIActivityManager; private final UserManager mUserManager; private final StorageStatsManager mStorageStatsManager; @GuardedBy("mLock") private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>(); Loading Loading @@ -147,6 +151,7 @@ public final class AppHibernationService extends SystemService { mPackageManagerInternal = injector.getPackageManagerInternal(); mIActivityManager = injector.getActivityManager(); mUserManager = injector.getUserManager(); mStorageStatsManager = injector.getStorageStatsManager(); mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); mBackgroundExecutor = injector.getBackgroundExecutor(); mOatArtifactDeletionEnabled = injector.isOatArtifactDeletionEnabled(); Loading Loading @@ -297,7 +302,8 @@ public final class AppHibernationService extends SystemService { pkgState.hibernated = isHibernating; if (isHibernating) { mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId)); mBackgroundExecutor.execute( () -> hibernatePackageForUser(packageName, realUserId, pkgState)); } else { mBackgroundExecutor.execute( () -> unhibernatePackageForUser(packageName, realUserId)); Loading Loading @@ -415,8 +421,9 @@ public final class AppHibernationService extends SystemService { + "the package was uninstalled? ", pkgName, userId)); continue; } HibernationStats stats = new HibernationStats( mGlobalHibernationStates.get(pkgName).savedByte); long diskBytesSaved = mGlobalHibernationStates.get(pkgName).savedByte + userPackageStates.get(pkgName).savedByte; HibernationStats stats = new HibernationStats(diskBytesSaved); statsMap.put(pkgName, stats); } } Loading @@ -427,16 +434,28 @@ public final class AppHibernationService extends SystemService { * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do * not hold {@link #mLock} while calling this to avoid deadlock scenarios. */ private void hibernatePackageForUser(@NonNull String packageName, int userId) { private void hibernatePackageForUser(@NonNull String packageName, int userId, UserLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { ApplicationInfo info = mIPackageManager.getApplicationInfo( packageName, PACKAGE_MATCH_FLAGS, userId); StorageStats stats = mStorageStatsManager.queryStatsForPackage( info.storageUuid, packageName, new UserHandle(userId)); mIActivityManager.forceStopPackage(packageName, userId); mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, null /* observer */); synchronized (mLock) { state.savedByte = stats.getCacheBytes(); } } catch (RemoteException e) { throw new IllegalStateException( "Failed to hibernate due to manager not being available", e); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Package name not found when querying storage stats", e); } catch (IOException e) { Slog.e(TAG, "Storage device not found", e); } finally { Binder.restoreCallingIdentity(caller); Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); Loading Loading @@ -920,6 +939,8 @@ public final class AppHibernationService extends SystemService { UserManager getUserManager(); StorageStatsManager getStorageStatsManager(); Executor getBackgroundExecutor(); UsageStatsManagerInternal getUsageStatsManagerInternal(); Loading Loading @@ -968,6 +989,11 @@ public final class AppHibernationService extends SystemService { return mContext.getSystemService(UserManager.class); } @Override public StorageStatsManager getStorageStatsManager() { return mContext.getSystemService(StorageStatsManager.class); } @Override public Executor getBackgroundExecutor() { return mScheduledExecutorService; Loading services/core/java/com/android/server/apphibernation/UserLevelState.java +4 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ final class UserLevelState { public String packageName; public boolean hibernated; // Saved bytes from user level hibernation. public long savedByte; @CurrentTimeMillisLong public long lastUnhibernatedMs; Loading @@ -36,6 +38,7 @@ final class UserLevelState { UserLevelState(UserLevelState state) { packageName = state.packageName; hibernated = state.hibernated; savedByte = state.savedByte; lastUnhibernatedMs = state.lastUnhibernatedMs; } Loading @@ -44,6 +47,7 @@ final class UserLevelState { return "UserLevelState{" + "packageName='" + packageName + '\'' + ", hibernated=" + hibernated + '\'' + ", savedByte=" + savedByte + '\'' + ", lastUnhibernated=" + DATE_FORMAT.format(lastUnhibernatedMs) + '}'; } Loading services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +38 −6 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ 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.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.doAnswer; Loading @@ -38,6 +40,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.IActivityManager; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; Loading @@ -48,6 +52,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; Loading @@ -68,10 +73,12 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; /** Loading Loading @@ -104,6 +111,8 @@ public final class AppHibernationServiceTest { @Mock private UserManager mUserManager; @Mock private StorageStatsManager mStorageStatsManager; @Mock private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; Loading @@ -115,7 +124,7 @@ public final class AppHibernationServiceTest { private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor; @Before public void setUp() throws RemoteException { public void setUp() throws RemoteException, PackageManager.NameNotFoundException, IOException { // Share class loader to allow access to package-private classes System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); Loading @@ -140,6 +149,11 @@ public final class AppHibernationServiceTest { packages.add(makePackageInfo(PACKAGE_NAME_3)); doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( longThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); doReturn(mock(ApplicationInfo.class)).when(mIPackageManager).getApplicationInfo( any(), anyLong(), anyInt()); StorageStats storageStats = new StorageStats(); doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage( (UUID) any(), anyString(), any()); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); UserInfo userInfo = addUser(USER_ID_1); Loading Loading @@ -381,18 +395,31 @@ public final class AppHibernationServiceTest { } @Test public void testGetHibernationStatsForUser_getsStatsForPackage() { // GIVEN a package is hibernating globally and for a user public void testGetHibernationStatsForUser_getsStatsForPackage() throws PackageManager.NameNotFoundException, IOException, RemoteException { // GIVEN a package is hibernating globally and for a user with some storage saved final long cacheSavings = 1000; StorageStats storageStats = new StorageStats(); storageStats.cacheBytes = cacheSavings; doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage( (UUID) any(), eq(PACKAGE_NAME_1), any()); final long oatDeletionSavings = 2000; doReturn(oatDeletionSavings).when(mPackageManagerInternal).deleteOatArtifactsOfPackage( PACKAGE_NAME_1); mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); // WHEN we ask for the hibernation stats for the package Map<String, HibernationStats> stats = Map<String, HibernationStats> statsMap = mAppHibernationService.getHibernationStatsForUser( Set.of(PACKAGE_NAME_1), USER_ID_1); // THEN the stats exist for the package assertTrue(stats.containsKey(PACKAGE_NAME_1)); // THEN the stats exist for the package and add up to the OAT deletion and cache deletion // savings HibernationStats stats = statsMap.get(PACKAGE_NAME_1); assertNotNull(stats); assertEquals(cacheSavings + oatDeletionSavings, stats.getDiskBytesSaved()); } @Test Loading Loading @@ -518,6 +545,11 @@ public final class AppHibernationServiceTest { return mUserManager; } @Override public StorageStatsManager getStorageStatsManager() { return mStorageStatsManager; } @Override public UsageStatsManagerInternal getUsageStatsManagerInternal() { return mUsageStatsManagerInternal; Loading Loading
services/core/java/com/android/server/apphibernation/AppHibernationService.java +30 −4 Original line number Diff line number Diff line Loading @@ -35,6 +35,8 @@ import android.app.ActivityThread; import android.app.IActivityManager; import android.app.StatsManager; import android.app.StatsManager.StatsPullAtomCallback; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; Loading Loading @@ -78,6 +80,7 @@ import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; Loading Loading @@ -111,6 +114,7 @@ public final class AppHibernationService extends SystemService { private final PackageManagerInternal mPackageManagerInternal; private final IActivityManager mIActivityManager; private final UserManager mUserManager; private final StorageStatsManager mStorageStatsManager; @GuardedBy("mLock") private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>(); Loading Loading @@ -147,6 +151,7 @@ public final class AppHibernationService extends SystemService { mPackageManagerInternal = injector.getPackageManagerInternal(); mIActivityManager = injector.getActivityManager(); mUserManager = injector.getUserManager(); mStorageStatsManager = injector.getStorageStatsManager(); mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); mBackgroundExecutor = injector.getBackgroundExecutor(); mOatArtifactDeletionEnabled = injector.isOatArtifactDeletionEnabled(); Loading Loading @@ -297,7 +302,8 @@ public final class AppHibernationService extends SystemService { pkgState.hibernated = isHibernating; if (isHibernating) { mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId)); mBackgroundExecutor.execute( () -> hibernatePackageForUser(packageName, realUserId, pkgState)); } else { mBackgroundExecutor.execute( () -> unhibernatePackageForUser(packageName, realUserId)); Loading Loading @@ -415,8 +421,9 @@ public final class AppHibernationService extends SystemService { + "the package was uninstalled? ", pkgName, userId)); continue; } HibernationStats stats = new HibernationStats( mGlobalHibernationStates.get(pkgName).savedByte); long diskBytesSaved = mGlobalHibernationStates.get(pkgName).savedByte + userPackageStates.get(pkgName).savedByte; HibernationStats stats = new HibernationStats(diskBytesSaved); statsMap.put(pkgName, stats); } } Loading @@ -427,16 +434,28 @@ public final class AppHibernationService extends SystemService { * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do * not hold {@link #mLock} while calling this to avoid deadlock scenarios. */ private void hibernatePackageForUser(@NonNull String packageName, int userId) { private void hibernatePackageForUser(@NonNull String packageName, int userId, UserLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { ApplicationInfo info = mIPackageManager.getApplicationInfo( packageName, PACKAGE_MATCH_FLAGS, userId); StorageStats stats = mStorageStatsManager.queryStatsForPackage( info.storageUuid, packageName, new UserHandle(userId)); mIActivityManager.forceStopPackage(packageName, userId); mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, null /* observer */); synchronized (mLock) { state.savedByte = stats.getCacheBytes(); } } catch (RemoteException e) { throw new IllegalStateException( "Failed to hibernate due to manager not being available", e); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Package name not found when querying storage stats", e); } catch (IOException e) { Slog.e(TAG, "Storage device not found", e); } finally { Binder.restoreCallingIdentity(caller); Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); Loading Loading @@ -920,6 +939,8 @@ public final class AppHibernationService extends SystemService { UserManager getUserManager(); StorageStatsManager getStorageStatsManager(); Executor getBackgroundExecutor(); UsageStatsManagerInternal getUsageStatsManagerInternal(); Loading Loading @@ -968,6 +989,11 @@ public final class AppHibernationService extends SystemService { return mContext.getSystemService(UserManager.class); } @Override public StorageStatsManager getStorageStatsManager() { return mContext.getSystemService(StorageStatsManager.class); } @Override public Executor getBackgroundExecutor() { return mScheduledExecutorService; Loading
services/core/java/com/android/server/apphibernation/UserLevelState.java +4 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ final class UserLevelState { public String packageName; public boolean hibernated; // Saved bytes from user level hibernation. public long savedByte; @CurrentTimeMillisLong public long lastUnhibernatedMs; Loading @@ -36,6 +38,7 @@ final class UserLevelState { UserLevelState(UserLevelState state) { packageName = state.packageName; hibernated = state.hibernated; savedByte = state.savedByte; lastUnhibernatedMs = state.lastUnhibernatedMs; } Loading @@ -44,6 +47,7 @@ final class UserLevelState { return "UserLevelState{" + "packageName='" + packageName + '\'' + ", hibernated=" + hibernated + '\'' + ", savedByte=" + savedByte + '\'' + ", lastUnhibernated=" + DATE_FORMAT.format(lastUnhibernatedMs) + '}'; } Loading
services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +38 −6 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ 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.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.doAnswer; Loading @@ -38,6 +40,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.IActivityManager; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; Loading @@ -48,6 +52,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; Loading @@ -68,10 +73,12 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; /** Loading Loading @@ -104,6 +111,8 @@ public final class AppHibernationServiceTest { @Mock private UserManager mUserManager; @Mock private StorageStatsManager mStorageStatsManager; @Mock private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; Loading @@ -115,7 +124,7 @@ public final class AppHibernationServiceTest { private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor; @Before public void setUp() throws RemoteException { public void setUp() throws RemoteException, PackageManager.NameNotFoundException, IOException { // Share class loader to allow access to package-private classes System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); Loading @@ -140,6 +149,11 @@ public final class AppHibernationServiceTest { packages.add(makePackageInfo(PACKAGE_NAME_3)); doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( longThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); doReturn(mock(ApplicationInfo.class)).when(mIPackageManager).getApplicationInfo( any(), anyLong(), anyInt()); StorageStats storageStats = new StorageStats(); doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage( (UUID) any(), anyString(), any()); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); UserInfo userInfo = addUser(USER_ID_1); Loading Loading @@ -381,18 +395,31 @@ public final class AppHibernationServiceTest { } @Test public void testGetHibernationStatsForUser_getsStatsForPackage() { // GIVEN a package is hibernating globally and for a user public void testGetHibernationStatsForUser_getsStatsForPackage() throws PackageManager.NameNotFoundException, IOException, RemoteException { // GIVEN a package is hibernating globally and for a user with some storage saved final long cacheSavings = 1000; StorageStats storageStats = new StorageStats(); storageStats.cacheBytes = cacheSavings; doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage( (UUID) any(), eq(PACKAGE_NAME_1), any()); final long oatDeletionSavings = 2000; doReturn(oatDeletionSavings).when(mPackageManagerInternal).deleteOatArtifactsOfPackage( PACKAGE_NAME_1); mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); // WHEN we ask for the hibernation stats for the package Map<String, HibernationStats> stats = Map<String, HibernationStats> statsMap = mAppHibernationService.getHibernationStatsForUser( Set.of(PACKAGE_NAME_1), USER_ID_1); // THEN the stats exist for the package assertTrue(stats.containsKey(PACKAGE_NAME_1)); // THEN the stats exist for the package and add up to the OAT deletion and cache deletion // savings HibernationStats stats = statsMap.get(PACKAGE_NAME_1); assertNotNull(stats); assertEquals(cacheSavings + oatDeletionSavings, stats.getDiskBytesSaved()); } @Test Loading Loading @@ -518,6 +545,11 @@ public final class AppHibernationServiceTest { return mUserManager; } @Override public StorageStatsManager getStorageStatsManager() { return mStorageStatsManager; } @Override public UsageStatsManagerInternal getUsageStatsManagerInternal() { return mUsageStatsManagerInternal; Loading