diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 366718c65d84b..3cd8060724a46 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -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; @@ -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; @@ -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> mUserStates = new SparseArray<>(); @@ -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(); @@ -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)); @@ -412,8 +418,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); } } @@ -424,16 +431,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); @@ -923,6 +942,8 @@ public final class AppHibernationService extends SystemService { UserManager getUserManager(); + StorageStatsManager getStorageStatsManager(); + Executor getBackgroundExecutor(); UsageStatsManagerInternal getUsageStatsManagerInternal(); @@ -971,6 +992,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; diff --git a/services/core/java/com/android/server/apphibernation/UserLevelState.java b/services/core/java/com/android/server/apphibernation/UserLevelState.java index 68c363c8256ae..6a489d28ac172 100644 --- a/services/core/java/com/android/server/apphibernation/UserLevelState.java +++ b/services/core/java/com/android/server/apphibernation/UserLevelState.java @@ -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; @@ -36,6 +38,7 @@ final class UserLevelState { UserLevelState(UserLevelState state) { packageName = state.packageName; hibernated = state.hibernated; + savedByte = state.savedByte; lastUnhibernatedMs = state.lastUnhibernatedMs; } @@ -44,6 +47,7 @@ final class UserLevelState { return "UserLevelState{" + "packageName='" + packageName + '\'' + ", hibernated=" + hibernated + '\'' + + ", savedByte=" + savedByte + '\'' + ", lastUnhibernated=" + DATE_FORMAT.format(lastUnhibernatedMs) + '}'; } 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 e1aa08d9176a7..51607e5282160 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -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; @@ -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; @@ -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; @@ -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; /** @@ -104,6 +111,8 @@ public final class AppHibernationServiceTest { @Mock private UserManager mUserManager; @Mock + private StorageStatsManager mStorageStatsManager; + @Mock private HibernationStateDiskStore mUserLevelDiskStore; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @@ -115,7 +124,7 @@ public final class AppHibernationServiceTest { private ArgumentCaptor 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); @@ -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); @@ -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 stats = + Map 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 @@ -518,6 +545,11 @@ public final class AppHibernationServiceTest { return mUserManager; } + @Override + public StorageStatsManager getStorageStatsManager() { + return mStorageStatsManager; + } + @Override public UsageStatsManagerInternal getUsageStatsManagerInternal() { return mUsageStatsManagerInternal;