Added throttle when reporting shortcut usage

Bug: 304290201
Test: manual
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:76121eb73d4c40829d5513b073871333520fe0a2)
Merged-In: I96370cbd4f6a55f894c1a93307e5f82dfd394652
Change-Id: I96370cbd4f6a55f894c1a93307e5f82dfd394652
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 1a71ca8..3e64396 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -32,6 +32,7 @@
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -46,6 +47,7 @@
 import android.os.Binder;
 import android.os.PersistableBundle;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.text.format.Formatter;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -56,6 +58,7 @@
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.ArrayUtils;
@@ -158,6 +161,9 @@
     private static final String KEY_BITMAPS = "bitmaps";
     private static final String KEY_BITMAP_BYTES = "bitmapBytes";
 
+    @VisibleForTesting
+    public static final int REPORT_USAGE_BUFFER_SIZE = 3;
+
     private final Object mLock = new Object();
 
     /**
@@ -195,6 +201,9 @@
     private boolean mIsNewApp;
     private List<ShortcutInfo> mManifestShortcuts;
 
+    @GuardedBy("mLock")
+    private List<Long> mLastReportedTime = new ArrayList<>();
+
     private ShortcutPackage(ShortcutUser shortcutUser,
             int packageUserId, String packageName, ShortcutPackageInfo spi) {
         super(shortcutUser, packageUserId, packageName,
@@ -1690,6 +1699,30 @@
         return condition[0];
     }
 
+    void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
+            @NonNull final String shortcutId) {
+        synchronized (mLock) {
+            final long currentTS = SystemClock.elapsedRealtime();
+            final ShortcutService s = mShortcutUser.mService;
+            if (mLastReportedTime.isEmpty()
+                    || mLastReportedTime.size() < REPORT_USAGE_BUFFER_SIZE) {
+                mLastReportedTime.add(currentTS);
+            } else if (currentTS - mLastReportedTime.get(0) > s.mSaveDelayMillis) {
+                mLastReportedTime.remove(0);
+                mLastReportedTime.add(currentTS);
+            } else {
+                return;
+            }
+            final long token = s.injectClearCallingIdentity();
+            try {
+                usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId,
+                        getUser().getUserId());
+            } finally {
+                s.injectRestoreCallingIdentity(token);
+            }
+        }
+    }
+
     public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index e83f14f..6eae823 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -361,7 +361,7 @@
     private CompressFormat mIconPersistFormat;
     private int mIconPersistQuality;
 
-    private int mSaveDelayMillis;
+    int mSaveDelayMillis;
 
     private final IPackageManager mIPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
@@ -2240,11 +2240,11 @@
                 List<ShortcutInfo> changedShortcuts = new ArrayList<>();
                 List<ShortcutInfo> removedShortcuts = null;
 
+                final ShortcutPackage ps;
                 synchronized (mLock) {
                     throwIfUserLockedL(userId);
 
-                    final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName,
-                            userId);
+                    ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
                     ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
                     fillInDefaultActivity(Arrays.asList(shortcut));
@@ -2282,7 +2282,7 @@
 
                 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
 
-                reportShortcutUsedInternal(packageName, shortcut.getId(), userId);
+                ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId());
 
                 verifyStates();
 
@@ -2861,11 +2861,11 @@
                             shortcutId, packageName, userId));
                 }
 
+                final ShortcutPackage ps;
                 synchronized (mLock) {
                     throwIfUserLockedL(userId);
 
-                    final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName,
-                            userId);
+                    ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
                     if (ps.findShortcutById(shortcutId) == null) {
                         Log.w(TAG, String.format(
@@ -2876,7 +2876,7 @@
                     }
                 }
 
-                reportShortcutUsedInternal(packageName, shortcutId, userId);
+                ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId);
                 ret.complete(true);
             } catch (Exception e) {
                 ret.completeExceptionally(e);
@@ -2885,15 +2885,6 @@
         return ret;
     }
 
-    private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) {
-        final long token = injectClearCallingIdentity();
-        try {
-            mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
-        } finally {
-            injectRestoreCallingIdentity(token);
-        }
-    }
-
     @Override
     public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
         final long token = injectClearCallingIdentity();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index ec5228f..9e4ebeb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -399,8 +399,8 @@
 
     public void testPushDynamicShortcut() {
         // Change the max number of shortcuts.
-        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5");
-
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+                + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
         setCaller(CALLING_PACKAGE_1, USER_0);
 
         final ShortcutInfo s1 = makeShortcut("s1");
@@ -538,6 +538,57 @@
                 eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0));
     }
 
+    public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+            throws InterruptedException {
+        mService.updateConfigurationLocked(
+                ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
+
+        // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled.
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        for (int i = 0; i < ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i++) {
+            final ShortcutInfo si = makeShortcut("s" + i);
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_1), eq("s1"), eq(USER_0));
+        Mockito.reset(mMockUsageStatsManagerInternal);
+        for (int i = ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i <= 10; i++) {
+            final ShortcutInfo si = makeShortcut("s" + i);
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                any(), any(), anyInt());
+
+        // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well.
+        setCaller(CALLING_PACKAGE_2, USER_0);
+        for (int i = 0; i < ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i++) {
+            final ShortcutInfo si = makeShortcut("s" + i);
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0));
+        Mockito.reset(mMockUsageStatsManagerInternal);
+        for (int i = ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i <= 10; i++) {
+            final ShortcutInfo si = makeShortcut("s" + i);
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                any(), any(), anyInt());
+
+        Mockito.reset(mMockUsageStatsManagerInternal);
+        // Let time passes which resets the throttle
+        Thread.sleep(505);
+        // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        mManager.pushDynamicShortcut(makeShortcut("s10"));
+        setCaller(CALLING_PACKAGE_2, USER_0);
+        mManager.pushDynamicShortcut(makeShortcut("s10"));
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_1), any(), eq(USER_0));
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_2), any(), eq(USER_0));
+    }
+
     public void testUnlimitedCalls() {
         setCaller(CALLING_PACKAGE_1, USER_0);
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 27091b7..afa21e7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -2136,6 +2136,8 @@
 
     public void testReportShortcutUsed() {
         mRunningUsers.put(USER_10, true);
+        mService.updateConfigurationLocked(
+                ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             reset(mMockUsageStatsManagerInternal);