From dfd04f3547b52132157047dbd52b1410d088f356 Mon Sep 17 00:00:00 2001 From: Varun Shah Date: Tue, 24 May 2022 17:55:01 -0700 Subject: [PATCH] Update standby buckets for restored apps. When apps are restored, set their standby bucket to RARE if they have been used within the past 90 days, based on the restored UsageStats data. Bug: 214580000 Test: atest UsageStatsDatabaseTest Change-Id: I9d63032ef84fef97734b51323f41b797caa4f554 (cherry picked from commit 89f69113ad3b4a593664b562ac735bc10c8c75f1) --- .../server/usage/AppStandbyInternal.java | 7 ++++ .../server/usage/AppStandbyController.java | 21 +++++++++++ core/java/android/app/usage/UsageStats.java | 11 ++++++ .../android/app/usage/UsageStatsManager.java | 8 ++++ .../server/usage/UsageStatsDatabaseTest.java | 15 ++++++-- .../server/usage/UsageStatsDatabase.java | 37 ++++++++++++++++++- .../server/usage/UsageStatsService.java | 3 +- .../server/usage/UserUsageStatsService.java | 5 ++- 8 files changed, 99 insertions(+), 8 deletions(-) diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index c43c832992cf9..9b64edf53d8cf 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -163,6 +163,13 @@ public interface AppStandbyInternal { int getAppStandbyBucketReason(@NonNull String packageName, @UserIdInt int userId, @ElapsedRealtimeLong long elapsedRealtime); + /** + * Puts the list of apps in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE} + * bucket. + * @param restoredApps the list of restored apps + */ + void restoreAppsToRare(@NonNull Set restoredApps, int userId); + /** * Put the specified app in the * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 9e3e3553c1256..5d9f3357125af 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; +import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_RESTORED; import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_UPDATE; import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY; import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION; @@ -1604,6 +1605,26 @@ public class AppStandbyController setAppStandbyBucket(packageName, userId, bucket, reason, nowElapsed, false); } + @Override + public void restoreAppsToRare(Set restoredApps, int userId) { + final int reason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED; + final long nowElapsed = mInjector.elapsedRealtime(); + for (String packageName : restoredApps) { + // If the package is not installed, don't allow the bucket to be set. + if (!mInjector.isPackageInstalled(packageName, 0, userId)) { + Slog.e(TAG, "Tried to restore bucket for uninstalled app: " + packageName); + continue; + } + + final int standbyBucket = getAppStandbyBucket(packageName, userId, nowElapsed, false); + // Only update the standby bucket to RARE if the app is still in the NEVER bucket. + if (standbyBucket == STANDBY_BUCKET_NEVER) { + setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RARE, reason, + nowElapsed, false); + } + } + } + @Override public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid) { diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index d61abc64fae5d..e213c934e95a3 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -292,6 +292,17 @@ public final class UsageStats implements Parcelable { return mLastTimeComponentUsed; } + /** + * Returns the last time the package was used - defined by the latest of + * mLastTimeUsed, mLastTimeVisible, mLastTimeForegroundServiceUsed, or mLastTimeComponentUsed. + * @hide + */ + public long getLastTimePackageUsed() { + return Math.max(mLastTimeUsed, + Math.max(mLastTimeVisible, + Math.max(mLastTimeForegroundServiceUsed, mLastTimeComponentUsed))); + } + /** * Returns the number of times the app was launched as an activity from outside of the app. * Excludes intra-app activity transitions. diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index c013fcd5adb96..1dfc7d48640e8 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -219,6 +219,11 @@ public final class UsageStatsManager { * @hide */ public static final int REASON_SUB_DEFAULT_APP_UPDATE = 0x0001; + /** + * The app was restored. + * @hide + */ + public static final int REASON_SUB_DEFAULT_APP_RESTORED = 0x0002; /** * The app was interacted with in some way by the system. * @hide @@ -1209,6 +1214,9 @@ public final class UsageStatsManager { case REASON_SUB_DEFAULT_APP_UPDATE: sb.append("-au"); break; + case REASON_SUB_DEFAULT_APP_RESTORED: + sb.append("-ar"); + break; } break; case REASON_MAIN_FORCED_BY_SYSTEM: diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index 75bd2ccbe635c..bc2c57e8bf848 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -22,6 +22,7 @@ import static junit.framework.TestCase.fail; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents.Event; @@ -440,6 +441,7 @@ public class UsageStatsDatabaseTest { prevDB.readMappingsLocked(); prevDB.init(1); prevDB.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats); + Set prevDBApps = mIntervalStats.packageStats.keySet(); // Create a backup with a specific version byte[] blob = prevDB.getBackupPayload(KEY_USAGE_STATS, version); if (version >= 1 && version <= 3) { @@ -447,6 +449,11 @@ public class UsageStatsDatabaseTest { "UsageStatsDatabase shouldn't be able to write backups as XML"); return; } + if (version < 1 || version > UsageStatsDatabase.BACKUP_VERSION) { + assertFalse(blob != null && blob.length != 0, + "UsageStatsDatabase shouldn't be able to write backups for unknown versions"); + return; + } clearUsageStatsFiles(); @@ -454,9 +461,11 @@ public class UsageStatsDatabaseTest { newDB.readMappingsLocked(); newDB.init(1); // Attempt to restore the usage stats from the backup - newDB.applyRestoredPayload(KEY_USAGE_STATS, blob); - List stats = newDB.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 0, mEndTime, - mIntervalStatsVerifier); + Set restoredApps = newDB.applyRestoredPayload(KEY_USAGE_STATS, blob); + assertTrue(restoredApps.containsAll(prevDBApps), + "List of restored apps does not match list backed-up apps list."); + List stats = newDB.queryUsageStats( + UsageStatsManager.INTERVAL_DAILY, 0, mEndTime, mIntervalStatsVerifier); if (version > UsageStatsDatabase.BACKUP_VERSION || version < 1) { assertFalse(stats != null && !stats.isEmpty(), diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index cc33f88d396d1..26a1e9d8af114 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -16,6 +16,7 @@ package com.android.server.usage; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; @@ -24,6 +25,7 @@ import android.app.usage.UsageStatsManager; import android.os.Build; import android.os.SystemProperties; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; @@ -55,8 +57,11 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Provides an interface to query for UsageStat data from a Protocol Buffer database. @@ -1252,6 +1257,10 @@ public class UsageStatsDatabase { Slog.wtf(TAG, "Attempting to backup UsageStats as XML with version " + version); return null; } + if (version < 1 || version > BACKUP_VERSION) { + Slog.wtf(TAG, "Attempting to backup UsageStats with an unknown version: " + version); + return null; + } synchronized (mLock) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (KEY_USAGE_STATS.equals(key)) { @@ -1300,14 +1309,26 @@ public class UsageStatsDatabase { } return baos.toByteArray(); } + } + /** + * Updates the set of packages given to only include those that have been used within the + * given timeframe (as defined by {@link UsageStats#getLastTimePackageUsed()}). + */ + private void calculatePackagesUsedWithinTimeframe( + IntervalStats stats, Set packagesList, long timeframeMs) { + for (UsageStats stat : stats.packageStats.values()) { + if (stat.getLastTimePackageUsed() > timeframeMs) { + packagesList.add(stat.mPackageName); + } + } } /** * @hide */ @VisibleForTesting - public void applyRestoredPayload(String key, byte[] payload) { + public @NonNull Set applyRestoredPayload(String key, byte[] payload) { synchronized (mLock) { if (KEY_USAGE_STATS.equals(key)) { // Read stats files for the current device configs @@ -1320,12 +1341,15 @@ public class UsageStatsDatabase { IntervalStats yearlyConfigSource = getLatestUsageStats(UsageStatsManager.INTERVAL_YEARLY); + final Set packagesRestored = new ArraySet<>(); try { DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload)); int backupDataVersion = in.readInt(); // Can't handle this backup set - if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) return; + if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) { + return packagesRestored; + } // Delete all stats files // Do this after reading version and before actually restoring @@ -1333,10 +1357,14 @@ public class UsageStatsDatabase { deleteDirectoryContents(mIntervalDirs[i]); } + // 90 days before today in epoch + final long timeframe = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90); int fileCount = in.readInt(); for (int i = 0; i < fileCount; i++) { IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in), backupDataVersion); + calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe); + packagesRestored.addAll(stats.packageStats.keySet()); stats = mergeStats(stats, dailyConfigSource); putUsageStats(UsageStatsManager.INTERVAL_DAILY, stats); } @@ -1345,6 +1373,7 @@ public class UsageStatsDatabase { for (int i = 0; i < fileCount; i++) { IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in), backupDataVersion); + calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe); stats = mergeStats(stats, weeklyConfigSource); putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, stats); } @@ -1353,6 +1382,7 @@ public class UsageStatsDatabase { for (int i = 0; i < fileCount; i++) { IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in), backupDataVersion); + calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe); stats = mergeStats(stats, monthlyConfigSource); putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, stats); } @@ -1361,6 +1391,7 @@ public class UsageStatsDatabase { for (int i = 0; i < fileCount; i++) { IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in), backupDataVersion); + calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe); stats = mergeStats(stats, yearlyConfigSource); putUsageStats(UsageStatsManager.INTERVAL_YEARLY, stats); } @@ -1370,7 +1401,9 @@ public class UsageStatsDatabase { } finally { indexFilesLocked(); } + return packagesRestored; } + return Collections.EMPTY_SET; } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index ef13cd964f6c8..f595c3de104e0 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -3034,7 +3034,8 @@ public class UsageStatsService extends SystemService implements if (userStats == null) { return; // user was stopped or removed } - userStats.applyRestoredPayload(key, payload); + final Set restoredApps = userStats.applyRestoredPayload(key, payload); + mAppStandby.restoreAppsToRare(restoredApps, user); } } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index c609add0b5d78..34c6c1623eeca 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -63,6 +63,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Set; /** * A per-user UsageStatsService. All methods are meant to be called with the main lock held @@ -1374,8 +1375,8 @@ class UserUsageStatsService { return mDatabase.getBackupPayload(key); } - void applyRestoredPayload(String key, byte[] payload){ + Set applyRestoredPayload(String key, byte[] payload) { checkAndGetTimeLocked(); - mDatabase.applyRestoredPayload(key, payload); + return mDatabase.applyRestoredPayload(key, payload); } }