Merge "Add a rate limitation of takeScreenshot() API" into rvc-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index 9050833..1169c91 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -48,6 +48,10 @@
     ctor public AccessibilityGestureEvent(int, int);
   }
 
+  public abstract class AccessibilityService extends android.app.Service {
+    field public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 1000; // 0x3e8
+  }
+
 }
 
 package android.animation {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 0a138cf..23cbdcd 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -572,6 +573,26 @@
      */
     public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000;
 
+    /**
+     * The interval time of calling
+     * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
+     * @hide
+     */
+    @TestApi
+    public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 1000;
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
+            "screenshot_hardwareBuffer";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
+            "screenshot_colorSpace";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
+            "screenshot_timestamp";
+
     private int mConnectionId = AccessibilityInteractionClient.NO_ID;
 
     @UnsupportedAppUsage
@@ -597,17 +618,6 @@
 
     private FingerprintGestureController mFingerprintGestureController;
 
-    /** @hide */
-    public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
-            "screenshot_hardwareBuffer";
-
-    /** @hide */
-    public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
-            "screenshot_colorSpace";
-
-    /** @hide */
-    public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
-            "screenshot_timestamp";
 
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
@@ -1926,10 +1936,9 @@
      *                  default display.
      * @param executor Executor on which to run the callback.
      * @param callback The callback invoked when the taking screenshot is done.
-     *                 The {@link AccessibilityService.ScreenshotResult} will be null for an
-     *                 invalid display.
      *
-     * @return {@code true} if the taking screenshot accepted, {@code false} if not.
+     * @return {@code true} if the taking screenshot accepted, {@code false} if too little time
+     * has elapsed since the last screenshot, invalid display or internal errors.
      */
     public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<ScreenshotResult> callback) {
@@ -1942,11 +1951,7 @@
             return false;
         }
         try {
-            connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
-                if (result == null) {
-                    sendScreenshotResult(executor, callback, null);
-                    return;
-                }
+            return connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
                 final HardwareBuffer hardwareBuffer =
                         result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
                 final ParcelableColorSpace colorSpace =
@@ -1959,7 +1964,6 @@
         } catch (RemoteException re) {
             throw new RuntimeException(re);
         }
-        return true;
     }
 
     /**
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 0b3b9b2..1b7b4af 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -110,7 +110,7 @@
 
     int getWindowIdForLeashToken(IBinder token);
 
-    void takeScreenshot(int displayId, in RemoteCallback callback);
+    boolean takeScreenshot(int displayId, in RemoteCallback callback);
 
     void setGestureDetectionPassthroughRegion(int displayId, in Region region);
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 75a7504..12f67a6 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -156,7 +156,9 @@
         return -1;
     }
 
-    public void takeScreenshot(int displayId, RemoteCallback callback) {}
+    public boolean takeScreenshot(int displayId, RemoteCallback callback) {
+        return false;
+    }
 
     public void setTouchExplorationPassthroughRegion(int displayId, Region region) {}
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 0a527d4..b905a39 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -27,6 +27,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
 import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
@@ -177,6 +178,8 @@
 
     final SparseArray<IBinder> mOverlayWindowTokens = new SparseArray();
 
+    /** The timestamp of requesting to take screenshot in milliseconds */
+    private long mRequestTakeScreenshotTimestampMs;
 
     public interface SystemSupport {
         /**
@@ -974,44 +977,39 @@
     }
 
     @Override
-    public void takeScreenshot(int displayId, RemoteCallback callback) {
+    public boolean takeScreenshot(int displayId, RemoteCallback callback) {
+        final long currentTimestamp = SystemClock.uptimeMillis();
+        if (mRequestTakeScreenshotTimestampMs != 0
+                && (currentTimestamp - mRequestTakeScreenshotTimestampMs)
+                <= AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
+            return false;
+        }
+        mRequestTakeScreenshotTimestampMs = currentTimestamp;
+
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
-                sendScreenshotResult(true, null, callback);
-                return;
+                return false;
             }
 
             if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
-                sendScreenshotResult(true, null, callback);
                 throw new SecurityException("Services don't have the capability of taking"
                         + " the screenshot.");
             }
         }
 
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
-            sendScreenshotResult(true, null, callback);
-            return;
+            return false;
         }
 
         final Display display = DisplayManagerGlobal.getInstance()
                 .getRealDisplay(displayId);
         if (display == null) {
-            sendScreenshotResult(true, null, callback);
-            return;
+            return false;
         }
 
-        sendScreenshotResult(false, display, callback);
-    }
-
-    private void sendScreenshotResult(boolean noResult, Display display, RemoteCallback callback) {
-        final boolean noScreenshot = noResult;
         final long identity = Binder.clearCallingIdentity();
         try {
             mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
-                if (noScreenshot) {
-                    callback.sendResult(null);
-                    return;
-                }
                 final Point displaySize = new Point();
                 // TODO (b/145893483): calling new API with the display as a parameter
                 // when surface control supported.
@@ -1041,6 +1039,8 @@
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
+
+        return true;
     }
 
     @Override
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index d1c3a02..4f5b9ed 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -328,6 +328,8 @@
         public void onFingerprintGesture(int gesture) {}
 
         @Override
-        public void takeScreenshot(int displayId, RemoteCallback callback) {}
+        public boolean takeScreenshot(int displayId, RemoteCallback callback) {
+            return false;
+        }
     }
 }