Merge "Introduce A11yService#takeScreenshotOfWindow()"
diff --git a/core/api/current.txt b/core/api/current.txt
index b64afea..a3738f7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3112,10 +3112,13 @@
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
     method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
+    method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
     field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1; // 0x1
     field public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3; // 0x3
     field public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4; // 0x4
+    field public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5; // 0x5
     field public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2; // 0x2
+    field public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6; // 0x6
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e86d2f3..2fe5d51 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -696,7 +696,8 @@
             ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
             ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
             ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
-            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
+            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+            ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
     })
     public @interface ScreenshotErrorCode {}
 
@@ -728,6 +729,18 @@
     public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
 
     /**
+     * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+    /**
+     * The status of taking screenshot is failure and the reason is the window contains secure
+     * content.
+     * @see WindowManager.LayoutParams#FLAG_SECURE
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+    /**
      * The interval time of calling
      * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
      * @hide
@@ -2568,6 +2581,7 @@
      * @param executor Executor on which to run the callback.
      * @param callback The callback invoked when taking screenshot has succeeded or failed.
      *                 See {@link TakeScreenshotCallback} for details.
+     * @see #takeScreenshotOfWindow
      */
     public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
             @NonNull TakeScreenshotCallback callback) {
@@ -2589,7 +2603,8 @@
                 final HardwareBuffer hardwareBuffer =
                         result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
                 final ParcelableColorSpace colorSpace =
-                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, android.graphics.ParcelableColorSpace.class);
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+                                android.graphics.ParcelableColorSpace.class);
                 final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
                         colorSpace.getColorSpace(),
                         result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
@@ -2601,6 +2616,37 @@
     }
 
     /**
+     * Takes a screenshot of the specified window and returns it via an
+     * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+     * to construct the bitmap from the ScreenshotResult's payload.
+     * <p>
+     * <strong>Note:</strong> In order to take screenshots your service has
+     * to declare the capability to take screenshot by setting the
+     * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * </p>
+     * <p>
+     * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+     * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+     * visually underneath an accessibility overlay (from your or another accessibility service) in
+     * order to capture the window contents without the screenshot being covered by the overlay
+     * contents drawn on the screen.
+     * </p>
+     *
+     * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when taking screenshot has succeeded or failed.
+     *                 See {@link TakeScreenshotCallback} for details.
+     * @see #takeScreenshot
+     */
+    public void takeScreenshotOfWindow(int accessibilityWindowId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull TakeScreenshotCallback callback) {
+        AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+                        mConnectionId, accessibilityWindowId, executor, callback);
+    }
+
+    /**
      * Sets the strokeWidth and color of the accessibility focus rectangle.
      * <p>
      * <strong>Note:</strong> This setting persists until this or another active
@@ -3113,7 +3159,8 @@
         private final @NonNull ColorSpace mColorSpace;
         private final long mTimestamp;
 
-        private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+        /** @hide */
+        public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
                 @NonNull ColorSpace colorSpace, long timestamp) {
             Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
             Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 9abce3a..da14b50 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -30,6 +30,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.AccessibilityWindowInfo;
 import java.util.List;
+import android.window.ScreenCapture;
 
 /**
  * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -122,6 +123,10 @@
 
     void takeScreenshot(int displayId, in RemoteCallback callback);
 
+    void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+        in ScreenCapture.ScreenCaptureListener listener,
+        IAccessibilityInteractionConnectionCallback callback);
+
     void setGestureDetectionPassthroughRegion(int displayId, in Region region);
 
     void setTouchExplorationPassthroughRegion(int displayId, in Region region);
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 510bde1..44168ca 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 
+import android.accessibilityservice.AccessibilityService;
 import android.annotation.NonNull;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -46,11 +47,13 @@
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityRequestPreparer;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -588,6 +591,43 @@
         }
     }
 
+    /**
+     * Take a screenshot using {@link ScreenCapture} of this {@link ViewRootImpl}'s {@link
+     * SurfaceControl}.
+     */
+    public void takeScreenshotOfWindowClientThread(int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {
+        Message message = PooledLambda.obtainMessage(
+                AccessibilityInteractionController::takeScreenshotOfWindowUiThread,
+                this, interactionId, listener, callback);
+
+        // Screenshot results are returned to the service asynchronously, so the same-thread
+        // message wait logic from #scheduleMessage() is not needed.
+        mHandler.sendMessage(message);
+    }
+
+    private void takeScreenshotOfWindowUiThread(int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {
+        try {
+            if ((mViewRootImpl.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, interactionId);
+                return;
+            }
+            final ScreenCapture.LayerCaptureArgs captureArgs =
+                    new ScreenCapture.LayerCaptureArgs.Builder(mViewRootImpl.getSurfaceControl())
+                            .setChildrenOnly(false).setUid(Process.myUid()).build();
+            if (ScreenCapture.captureLayers(captureArgs, listener) != 0) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+            }
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        }
+    }
+
     public void findFocusClientThread(long accessibilityNodeId, int focusType,
             Region interactiveRegion, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 63602393..7e8ebd7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -88,6 +88,7 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
 import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
 import android.animation.AnimationHandler;
 import android.animation.LayoutTransition;
 import android.annotation.AnyThread;
@@ -199,6 +200,7 @@
 import android.window.CompatOnBackInvokedCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
+import android.window.ScreenCapture;
 import android.window.SurfaceSyncGroup;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -10707,6 +10709,25 @@
                         .notifyOutsideTouchClientThread();
             }
         }
+
+        @Override
+        public void takeScreenshotOfWindow(int interactionId,
+                ScreenCapture.ScreenCaptureListener listener,
+                IAccessibilityInteractionConnectionCallback callback) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                        .takeScreenshotOfWindowClientThread(interactionId, listener, callback);
+            } else {
+                try {
+                    callback.sendTakeScreenshotOfWindowError(
+                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                            interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index e3ffc9d..7030ab5 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -22,7 +22,9 @@
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -31,17 +33,21 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
 import android.view.Display;
 import android.view.ViewConfiguration;
+import android.window.ScreenCapture;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -53,6 +59,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -146,6 +153,10 @@
 
     private boolean mPerformAccessibilityActionResult;
 
+    // SparseArray of interaction ID -> screenshot executor+callback.
+    private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
+            mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
+
     private Message mSameThreadMessage;
 
     private int mInteractionIdWaitingForPrefetchResult = -1;
@@ -779,6 +790,59 @@
     }
 
     /**
+     * Takes a screenshot of the window with the provided {@code accessibilityWindowId} and
+     * returns the answer asynchronously. This async behavior is similar to {@link
+     * AccessibilityService#takeScreenshot} but unlike other methods in this class which perform
+     * synchronous waiting in the AccessibilityService client.
+     *
+     * @see AccessibilityService#takeScreenshotOfWindow
+     */
+    public void takeScreenshotOfWindow(int connectionId, int accessibilityWindowId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AccessibilityService.TakeScreenshotCallback callback) {
+        synchronized (mInstanceLock) {
+            try {
+                IAccessibilityServiceConnection connection = getConnection(connectionId);
+                if (connection == null) {
+                    executor.execute(() -> callback.onFailure(
+                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+                    return;
+                }
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    final int interactionId = mInteractionIdCounter.getAndIncrement();
+                    mTakeScreenshotOfWindowCallbacks.put(interactionId,
+                            Pair.create(executor, callback));
+                    // Create a ScreenCaptureListener to receive the screenshot directly from
+                    // SurfaceFlinger instead of requiring an extra IPC from the app:
+                    //   A11yService -> App -> SurfaceFlinger -> A11yService
+                    ScreenCapture.ScreenCaptureListener listener =
+                            new ScreenCapture.ScreenCaptureListener(
+                                    screenshot -> sendWindowScreenshotSuccess(screenshot,
+                                            interactionId));
+                    connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
+                            listener, this);
+                    new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                        synchronized (mInstanceLock) {
+                            // Notify failure if we still haven't sent a response after timeout.
+                            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                                sendTakeScreenshotOfWindowError(
+                                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                                        interactionId);
+                            }
+                        }
+                    }, TIMEOUT_INTERACTION_MILLIS);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+            } catch (RemoteException re) {
+                executor.execute(() -> callback.onFailure(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+            }
+        }
+    }
+
+    /**
      * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
      * insensitive containment. The search is performed in the window whose
      * id is specified and starts from the node whose accessibility id is
@@ -1254,6 +1318,55 @@
     }
 
     /**
+     * Sends the result of a window screenshot request to the requesting client.
+     *
+     * {@link #takeScreenshotOfWindow} does not perform synchronous waiting, so this method
+     * does not notify any wait lock.
+     */
+    private void sendWindowScreenshotSuccess(ScreenCapture.ScreenshotHardwareBuffer screenshot,
+            int interactionId) {
+        if (screenshot == null) {
+            sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+            return;
+        }
+        synchronized (mInstanceLock) {
+            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                final AccessibilityService.ScreenshotResult result =
+                        new AccessibilityService.ScreenshotResult(screenshot.getHardwareBuffer(),
+                                screenshot.getColorSpace(), SystemClock.uptimeMillis());
+                final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+                        mTakeScreenshotOfWindowCallbacks.get(interactionId);
+                final Executor executor = pair.first;
+                final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+                executor.execute(() -> callback.onSuccess(result));
+                mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+            }
+        }
+    }
+
+    /**
+     * Sends an error code for a window screenshot request to the requesting client.
+     *
+     * @param errorCode The error code from {@link AccessibilityService.ScreenshotErrorCode}.
+     * @param interactionId The interaction id of the request.
+     */
+    @Override
+    public void sendTakeScreenshotOfWindowError(
+            @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
+        synchronized (mInstanceLock) {
+            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+                        mTakeScreenshotOfWindowCallbacks.get(interactionId);
+                final Executor executor = pair.first;
+                final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+                executor.execute(() -> callback.onFailure(errorCode));
+                mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+            }
+        }
+    }
+
+    /**
      * Clears the result state.
      */
     private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 472a363..fb01921 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -22,6 +22,7 @@
 import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 /**
  * Interface for interaction between the AccessibilityManagerService
@@ -60,4 +61,8 @@
     void clearAccessibilityFocus();
 
     void notifyOutsideTouch();
+
+    void takeScreenshotOfWindow(int interactionId,
+        in ScreenCapture.ScreenCaptureListener listener,
+        IAccessibilityInteractionConnectionCallback callback);
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..456bf58 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -63,4 +63,9 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+
+    /**
+    * Sends an error code for a window screenshot request to the requesting client.
+    */
+    void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 35d5948..7a5ab045 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -27,6 +27,7 @@
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.window.ScreenCapture;
 
 import java.util.Collections;
 import java.util.List;
@@ -180,6 +181,10 @@
 
     public void takeScreenshot(int displayId, RemoteCallback callback) {}
 
+    public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {}
+
     public void setFocusAppearance(int strokeWidth, int color) {}
 
     public void setCacheEnabled(boolean enabled) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7365b95..1f7a7fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -29,6 +29,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 import androidx.annotation.BinderThread;
 
@@ -362,6 +363,15 @@
         }
 
         @Override
+        public void takeScreenshotOfWindow(int interactionId,
+                ScreenCapture.ScreenCaptureListener listener,
+                IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+            // AbstractAccessibilityServiceConnection uses the standard
+            // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows,
+            // so do nothing here.
+        }
+
+        @Override
         public void clearAccessibilityFocus() throws RemoteException {
             // Do nothing
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f35de17..c77b597 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
@@ -83,6 +84,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
 import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import com.android.internal.annotations.GuardedBy;
@@ -211,6 +213,11 @@
 
     /** The timestamp of requesting to take screenshot in milliseconds */
     private long mRequestTakeScreenshotTimestampMs;
+    /**
+     * The timestamps of requesting to take a window screenshot in milliseconds,
+     * mapping from accessibility window id -> timestamp.
+     */
+    private SparseArray<Long> mRequestTakeScreenshotOfWindowTimestampMs = new SparseArray<>();
 
     public interface SystemSupport {
         /**
@@ -1252,6 +1259,51 @@
     }
 
     @Override
+    public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+        final long currentTimestamp = SystemClock.uptimeMillis();
+        if ((currentTimestamp
+                - mRequestTakeScreenshotOfWindowTimestampMs.get(accessibilityWindowId, 0L))
+                <= ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT, interactionId);
+            return;
+        }
+        mRequestTakeScreenshotOfWindowTimestampMs.put(accessibilityWindowId, currentTimestamp);
+
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+                return;
+            }
+            if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+                        interactionId);
+                return;
+            }
+        }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+                    interactionId);
+            return;
+        }
+
+        RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
+                mSystemSupport.getCurrentUserIdLocked(),
+                resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+        if (connection == null) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
+            return;
+        }
+        connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
+    }
+
+    @Override
     public void takeScreenshot(int displayId, RemoteCallback callback) {
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6958b66..c08b6ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -167,6 +167,12 @@
         mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId);
     }
 
+    @Override
+    public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
+            throws RemoteException {
+        mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
+    }
+
     private void replaceInfoActionsAndCallService() {
         final AccessibilityNodeInfo nodeToReturn;
         boolean doCallback = false;