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;