Merge "Reply error through callback for takeScreenshot()" into rvc-dev
This commit is contained in:
@@ -2879,7 +2879,11 @@ package android.accessibilityservice {
|
||||
method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
|
||||
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
|
||||
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
|
||||
method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>);
|
||||
method public void takeScreenshot(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_NO_ACCESSIBILITY_ACCESS = 2; // 0x2
|
||||
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
|
||||
@@ -2980,6 +2984,11 @@ package android.accessibilityservice {
|
||||
method public void onShowModeChanged(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
|
||||
}
|
||||
|
||||
public static interface AccessibilityService.TakeScreenshotCallback {
|
||||
method public void onFailure(int);
|
||||
method public void onSuccess(@NonNull android.accessibilityservice.AccessibilityService.ScreenshotResult);
|
||||
}
|
||||
|
||||
public class AccessibilityServiceInfo implements android.os.Parcelable {
|
||||
ctor public AccessibilityServiceInfo();
|
||||
method public static String capabilityToString(int);
|
||||
|
||||
@@ -33,7 +33,6 @@ import android.graphics.ColorSpace;
|
||||
import android.graphics.ParcelableColorSpace;
|
||||
import android.graphics.Region;
|
||||
import android.hardware.HardwareBuffer;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
@@ -575,6 +574,46 @@ public abstract class AccessibilityService extends Service {
|
||||
*/
|
||||
public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000;
|
||||
|
||||
/**
|
||||
* Annotations for error codes of taking screenshot.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(prefix = { "TAKE_SCREENSHOT_" }, value = {
|
||||
ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
|
||||
ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
|
||||
ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
|
||||
ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
|
||||
})
|
||||
public @interface ScreenshotErrorCode {}
|
||||
|
||||
/**
|
||||
* The status of taking screenshot is success.
|
||||
* @hide
|
||||
*/
|
||||
public static final int TAKE_SCREENSHOT_SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* The status of taking screenshot is failure and the reason is internal error.
|
||||
*/
|
||||
public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1;
|
||||
|
||||
/**
|
||||
* The status of taking screenshot is failure and the reason is no accessibility access.
|
||||
*/
|
||||
public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2;
|
||||
|
||||
/**
|
||||
* The status of taking screenshot is failure and the reason is that too little time has
|
||||
* elapsed since the last screenshot.
|
||||
*/
|
||||
public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3;
|
||||
|
||||
/**
|
||||
* The status of taking screenshot is failure and the reason is invalid display Id.
|
||||
*/
|
||||
public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
|
||||
|
||||
/**
|
||||
* The interval time of calling
|
||||
* {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
|
||||
@@ -583,6 +622,10 @@ public abstract class AccessibilityService extends Service {
|
||||
@TestApi
|
||||
public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 1000;
|
||||
|
||||
/** @hide */
|
||||
public static final String KEY_ACCESSIBILITY_SCREENSHOT_STATUS =
|
||||
"screenshot_status";
|
||||
|
||||
/** @hide */
|
||||
public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
|
||||
"screenshot_hardwareBuffer";
|
||||
@@ -1945,14 +1988,13 @@ public abstract class AccessibilityService extends Service {
|
||||
* @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
|
||||
* default display.
|
||||
* @param executor Executor on which to run the callback.
|
||||
* @param callback The callback invoked when the taking screenshot is done.
|
||||
* @param callback The callback invoked when taking screenshot has succeeded or failed.
|
||||
* See {@link TakeScreenshotCallback} for details.
|
||||
*
|
||||
* @return {@code true} if the taking screenshot accepted, {@code false} if too little time
|
||||
* has elapsed since the last screenshot, invalid display or internal errors.
|
||||
* @throws IllegalArgumentException if displayId is not {@link Display#DEFAULT_DISPLAY}.
|
||||
*/
|
||||
public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Consumer<ScreenshotResult> callback) {
|
||||
public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull TakeScreenshotCallback callback) {
|
||||
Preconditions.checkNotNull(executor, "executor cannot be null");
|
||||
Preconditions.checkNotNull(callback, "callback cannot be null");
|
||||
|
||||
@@ -1964,18 +2006,24 @@ public abstract class AccessibilityService extends Service {
|
||||
AccessibilityInteractionClient.getInstance().getConnection(
|
||||
mConnectionId);
|
||||
if (connection == null) {
|
||||
return false;
|
||||
sendScreenshotFailure(ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, executor, callback);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
|
||||
connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
|
||||
final int status = result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS);
|
||||
if (status != TAKE_SCREENSHOT_SUCCESS) {
|
||||
sendScreenshotFailure(status, executor, callback);
|
||||
return;
|
||||
}
|
||||
final HardwareBuffer hardwareBuffer =
|
||||
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
|
||||
final ParcelableColorSpace colorSpace =
|
||||
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE);
|
||||
ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
|
||||
final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
|
||||
colorSpace.getColorSpace(),
|
||||
result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
|
||||
sendScreenshotResult(executor, callback, screenshot);
|
||||
sendScreenshotSuccess(screenshot, executor, callback);
|
||||
}));
|
||||
} catch (RemoteException re) {
|
||||
throw new RuntimeException(re);
|
||||
@@ -2374,15 +2422,32 @@ public abstract class AccessibilityService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendScreenshotResult(Executor executor, Consumer<ScreenshotResult> callback,
|
||||
ScreenshotResult screenshot) {
|
||||
final ScreenshotResult result = screenshot;
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> callback.accept(result));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
private void sendScreenshotSuccess(ScreenshotResult screenshot, Executor executor,
|
||||
TakeScreenshotCallback callback) {
|
||||
executor.execute(() -> callback.onSuccess(screenshot));
|
||||
}
|
||||
|
||||
private void sendScreenshotFailure(@ScreenshotErrorCode int errorCode, Executor executor,
|
||||
TakeScreenshotCallback callback) {
|
||||
executor.execute(() -> callback.onFailure(errorCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to report status of taking screenshot.
|
||||
*/
|
||||
public interface TakeScreenshotCallback {
|
||||
/** Called when taking screenshot has completed successfully.
|
||||
*
|
||||
* @param screenshot The content of screenshot.
|
||||
*/
|
||||
void onSuccess(@NonNull ScreenshotResult screenshot);
|
||||
|
||||
/** Called when taking screenshot has failed. {@code errorCode} will identify the
|
||||
* reason of failure.
|
||||
*
|
||||
* @param errorCode The error code of this operation.
|
||||
*/
|
||||
void onFailure(@ScreenshotErrorCode int errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,7 +110,7 @@ interface IAccessibilityServiceConnection {
|
||||
|
||||
int getWindowIdForLeashToken(IBinder token);
|
||||
|
||||
boolean takeScreenshot(int displayId, in RemoteCallback callback);
|
||||
void takeScreenshot(int displayId, in RemoteCallback callback);
|
||||
|
||||
void setGestureDetectionPassthroughRegion(int displayId, in Region region);
|
||||
|
||||
|
||||
@@ -156,9 +156,7 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean takeScreenshot(int displayId, RemoteCallback callback) {
|
||||
return false;
|
||||
}
|
||||
public void takeScreenshot(int displayId, RemoteCallback callback) {}
|
||||
|
||||
public void setTouchExplorationPassthroughRegion(int displayId, Region region) {}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.accessibility;
|
||||
|
||||
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;
|
||||
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP;
|
||||
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
|
||||
@@ -66,6 +67,7 @@ import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MagnificationSpec;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceControl.ScreenshotGraphicBuffer;
|
||||
import android.view.View;
|
||||
import android.view.WindowInfo;
|
||||
import android.view.accessibility.AccessibilityCache;
|
||||
@@ -977,18 +979,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean takeScreenshot(int displayId, RemoteCallback callback) {
|
||||
public void takeScreenshot(int displayId, RemoteCallback callback) {
|
||||
final long currentTimestamp = SystemClock.uptimeMillis();
|
||||
if (mRequestTakeScreenshotTimestampMs != 0
|
||||
&& (currentTimestamp - mRequestTakeScreenshotTimestampMs)
|
||||
<= AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
|
||||
return false;
|
||||
sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
mRequestTakeScreenshotTimestampMs = currentTimestamp;
|
||||
|
||||
synchronized (mLock) {
|
||||
if (!hasRightsToCurrentUserLocked()) {
|
||||
return false;
|
||||
sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
|
||||
@@ -998,43 +1004,57 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
|
||||
}
|
||||
|
||||
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
|
||||
return false;
|
||||
sendScreenshotFailure(
|
||||
AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
|
||||
final Display display = DisplayManagerGlobal.getInstance()
|
||||
.getRealDisplay(displayId);
|
||||
if (display == null) {
|
||||
return false;
|
||||
sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
|
||||
sendScreenshotSuccess(display, callback);
|
||||
}
|
||||
|
||||
private ScreenshotGraphicBuffer takeScreenshotBuffer(Display display) {
|
||||
final Point displaySize = new Point();
|
||||
// TODO (b/145893483): calling new API with the display as a parameter
|
||||
// when surface control supported.
|
||||
final IBinder token = SurfaceControl.getInternalDisplayToken();
|
||||
final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
|
||||
final int rotation = display.getRotation();
|
||||
display.getRealSize(displaySize);
|
||||
|
||||
return SurfaceControl.screenshotToBuffer(token, crop, displaySize.x, displaySize.y,
|
||||
false, rotation);
|
||||
}
|
||||
|
||||
private void sendScreenshotSuccess(Display display, RemoteCallback callback) {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
|
||||
final Point displaySize = new Point();
|
||||
// TODO (b/145893483): calling new API with the display as a parameter
|
||||
// when surface control supported.
|
||||
final IBinder token = SurfaceControl.getInternalDisplayToken();
|
||||
final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
|
||||
final int rotation = display.getRotation();
|
||||
display.getRealSize(displaySize);
|
||||
|
||||
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
|
||||
SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, crop,
|
||||
displaySize.x, displaySize.y, false,
|
||||
rotation);
|
||||
final ScreenshotGraphicBuffer screenshotBuffer = takeScreenshotBuffer(display);
|
||||
final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
|
||||
try (HardwareBuffer hardwareBuffer =
|
||||
HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
|
||||
HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
|
||||
final ParcelableColorSpace colorSpace =
|
||||
new ParcelableColorSpace(screenshotBuffer.getColorSpace());
|
||||
|
||||
// Send back the result.
|
||||
final Bundle payload = new Bundle();
|
||||
payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
|
||||
AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
|
||||
payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
|
||||
hardwareBuffer);
|
||||
payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
|
||||
payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
|
||||
SystemClock.uptimeMillis());
|
||||
|
||||
// Send back the result.
|
||||
callback.sendResult(payload);
|
||||
hardwareBuffer.close();
|
||||
}
|
||||
@@ -1042,8 +1062,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode,
|
||||
RemoteCallback callback) {
|
||||
mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
|
||||
final Bundle payload = new Bundle();
|
||||
payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS, errorCode);
|
||||
// Send back the result.
|
||||
callback.sendResult(payload);
|
||||
}, null).recycleOnUse());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -328,8 +328,6 @@ class UiAutomationManager {
|
||||
public void onFingerprintGesture(int gesture) {}
|
||||
|
||||
@Override
|
||||
public boolean takeScreenshot(int displayId, RemoteCallback callback) {
|
||||
return false;
|
||||
}
|
||||
public void takeScreenshot(int displayId, RemoteCallback callback) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -698,18 +697,8 @@ public class AbstractAccessibilityServiceConnectionTest {
|
||||
assertThat(result, is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void takeScreenshot_returnNull() {
|
||||
// no checkAccessibilityAccess, should return null.
|
||||
when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
|
||||
when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
|
||||
mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> {
|
||||
assertNull(result);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test (expected = SecurityException.class)
|
||||
public void takeScreenshot_throwSecurityException() {
|
||||
public void takeScreenshot_withoutCapability_throwSecurityException() {
|
||||
// no canTakeScreenshot, should throw security exception.
|
||||
when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
|
||||
mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> {
|
||||
|
||||
Reference in New Issue
Block a user