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 3d4610f9e330c..4d4d1ff95b41e 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -3047,7 +3047,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); } }