Introduce A11yService#takeScreenshotOfWindow()
This enables accessibility services to take a screenshot of
a window without any overlay windows drawn on top, which is
beneficial for ML-based understanding of the visual content
in the window.
Bug: 249830826
Test: atest AccessibilityTakeScreenshotTest
Test: use in an a11y service, observe screenshots
contain only content from the requested windows
and no overlays
Change-Id: Ic886aeace9cb2984a472273858281b4f2c3a0e46
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
* <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>
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
|
||||
mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
|
||||
|
||||
private Message mSameThreadMessage;
|
||||
|
||||
private int mInteractionIdWaitingForPrefetchResult = -1;
|
||||
@@ -778,6 +789,59 @@ public final class AccessibilityInteractionClient
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -1253,6 +1317,55 @@ public final class AccessibilityInteractionClient
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.os.Bundle;
|
||||
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 @@ oneway interface IAccessibilityInteractionConnection {
|
||||
void clearAccessibilityFocus();
|
||||
|
||||
void notifyOutsideTouch();
|
||||
|
||||
void takeScreenshotOfWindow(int interactionId,
|
||||
in ScreenCapture.ScreenCaptureListener listener,
|
||||
IAccessibilityInteractionConnectionCallback callback);
|
||||
}
|
||||
|
||||
@@ -63,4 +63,9 @@ oneway interface IAccessibilityInteractionConnectionCallback {
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.Bundle;
|
||||
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 class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon
|
||||
|
||||
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) {}
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityWindowInfo;
|
||||
import android.view.accessibility.IAccessibilityInteractionConnection;
|
||||
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
|
||||
import android.window.ScreenCapture;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
|
||||
@@ -361,6 +362,15 @@ public class PipAccessibilityInteractionConnection {
|
||||
});
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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.AccessibilityNodeInfo;
|
||||
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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
|
||||
|
||||
/** 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 {
|
||||
/**
|
||||
@@ -1251,6 +1258,51 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
|
||||
mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
|
||||
}
|
||||
|
||||
@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()) {
|
||||
|
||||
@@ -167,6 +167,12 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user