diff --git a/core/api/current.txt b/core/api/current.txt index 80bae5bd071dd..7cd7725758fe0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3112,10 +3112,13 @@ package android.accessibilityservice { 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 e86d2f35ef16c..2fe5d5140371a 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -696,7 +696,8 @@ public abstract class AccessibilityService extends Service { 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 {} @@ -727,6 +728,18 @@ public abstract class AccessibilityService extends Service { */ 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. @@ -2568,6 +2581,7 @@ public abstract class AccessibilityService extends Service { * @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 @@ public abstract class AccessibilityService extends Service { 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)); @@ -2600,6 +2615,37 @@ public abstract class AccessibilityService extends Service { } } + /** + * 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. + *
+ * Note: 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}. + *
+ *
+ * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+ * screen understanding. Use takeScreenshotOfWindow 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.
+ *
@@ -3113,7 +3159,8 @@ public abstract class AccessibilityService extends Service {
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 9abce3a399441..da14b50e64817 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -30,6 +30,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
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 @@ interface IAccessibilityServiceConnection {
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 510bde1deb5d8..44168ca096cfa 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.ACTION_ARGUMENT_A
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.AccessibilityNodeInfo.AccessibilityAction;
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 @@ public final class AccessibilityInteractionController {
}
}
+ /**
+ * 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 e664ebf4c4849..fdc6412602a62 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.InputMethodCl
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;
@@ -198,6 +199,7 @@ import android.window.ClientWindowFrames;
import android.window.CompatOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import android.window.ScreenCapture;
import android.window.SurfaceSyncGroup;
import android.window.WindowOnBackInvokedDispatcher;
@@ -10691,6 +10693,25 @@ public final class ViewRootImpl implements ViewParent,
.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 e3ffc9dcbbdec..7030ab54b7905 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -22,7 +22,9 @@ import static android.os.Build.VERSION_CODES.S;
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.content.Context;
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.Collections;
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 @@ public final class AccessibilityInteractionClient
private boolean mPerformAccessibilityActionResult;
+ // SparseArray of interaction ID -> screenshot executor+callback.
+ private final SparseArray