Merge "Reply error through callback for takeScreenshot()" into rvc-dev am: 658dd8047d

Change-Id: Ieeaf65810b4bcbccbf43d9d0207000b00f4c9481
This commit is contained in:
Jacky Kao
2020-03-20 04:20:23 +00:00
committed by Automerger Merge Worker
7 changed files with 146 additions and 59 deletions

View File

@@ -2879,7 +2879,11 @@ package android.accessibilityservice {
method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region); method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region); 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 = 20; // 0x14
field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28 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 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); 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 { public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo(); ctor public AccessibilityServiceInfo();
method public static String capabilityToString(int); method public static String capabilityToString(int);

View File

@@ -33,7 +33,6 @@ import android.graphics.ColorSpace;
import android.graphics.ParcelableColorSpace; import android.graphics.ParcelableColorSpace;
import android.graphics.Region; import android.graphics.Region;
import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
@@ -575,6 +574,46 @@ public abstract class AccessibilityService extends Service {
*/ */
public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000; 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 * The interval time of calling
* {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API. * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
@@ -583,6 +622,10 @@ public abstract class AccessibilityService extends Service {
@TestApi @TestApi
public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 1000; public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 1000;
/** @hide */
public static final String KEY_ACCESSIBILITY_SCREENSHOT_STATUS =
"screenshot_status";
/** @hide */ /** @hide */
public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER = public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
"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 * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
* default display. * default display.
* @param executor Executor on which to run the callback. * @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}. * @throws IllegalArgumentException if displayId is not {@link Display#DEFAULT_DISPLAY}.
*/ */
public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor, public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<ScreenshotResult> callback) { @NonNull TakeScreenshotCallback callback) {
Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(executor, "executor cannot be null");
Preconditions.checkNotNull(callback, "callback cannot be null"); Preconditions.checkNotNull(callback, "callback cannot be null");
@@ -1964,18 +2006,24 @@ public abstract class AccessibilityService extends Service {
AccessibilityInteractionClient.getInstance().getConnection( AccessibilityInteractionClient.getInstance().getConnection(
mConnectionId); mConnectionId);
if (connection == null) { if (connection == null) {
return false; sendScreenshotFailure(ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, executor, callback);
return;
} }
try { 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 = final HardwareBuffer hardwareBuffer =
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER); result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
final ParcelableColorSpace colorSpace = final ParcelableColorSpace colorSpace =
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE); result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE);
ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer, final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
colorSpace.getColorSpace(), colorSpace.getColorSpace(),
result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP)); result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
sendScreenshotResult(executor, callback, screenshot); sendScreenshotSuccess(screenshot, executor, callback);
})); }));
} catch (RemoteException re) { } catch (RemoteException re) {
throw new RuntimeException(re); throw new RuntimeException(re);
@@ -2374,15 +2422,32 @@ public abstract class AccessibilityService extends Service {
} }
} }
private void sendScreenshotResult(Executor executor, Consumer<ScreenshotResult> callback, private void sendScreenshotSuccess(ScreenshotResult screenshot, Executor executor,
ScreenshotResult screenshot) { TakeScreenshotCallback callback) {
final ScreenshotResult result = screenshot; executor.execute(() -> callback.onSuccess(screenshot));
final long identity = Binder.clearCallingIdentity(); }
try {
executor.execute(() -> callback.accept(result)); private void sendScreenshotFailure(@ScreenshotErrorCode int errorCode, Executor executor,
} finally { TakeScreenshotCallback callback) {
Binder.restoreCallingIdentity(identity); 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);
} }
/** /**

View File

@@ -110,7 +110,7 @@ interface IAccessibilityServiceConnection {
int getWindowIdForLeashToken(IBinder token); 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); void setGestureDetectionPassthroughRegion(int displayId, in Region region);

View File

@@ -156,9 +156,7 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon
return -1; return -1;
} }
public boolean takeScreenshot(int displayId, RemoteCallback callback) { public void takeScreenshot(int displayId, RemoteCallback callback) {}
return false;
}
public void setTouchExplorationPassthroughRegion(int displayId, Region region) {} public void setTouchExplorationPassthroughRegion(int displayId, Region region) {}

View File

@@ -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_COLORSPACE;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER; 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.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP;
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
@@ -66,6 +67,7 @@ import android.view.Display;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MagnificationSpec; import android.view.MagnificationSpec;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceControl.ScreenshotGraphicBuffer;
import android.view.View; import android.view.View;
import android.view.WindowInfo; import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache; import android.view.accessibility.AccessibilityCache;
@@ -977,18 +979,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
} }
@Override @Override
public boolean takeScreenshot(int displayId, RemoteCallback callback) { public void takeScreenshot(int displayId, RemoteCallback callback) {
final long currentTimestamp = SystemClock.uptimeMillis(); final long currentTimestamp = SystemClock.uptimeMillis();
if (mRequestTakeScreenshotTimestampMs != 0 if (mRequestTakeScreenshotTimestampMs != 0
&& (currentTimestamp - mRequestTakeScreenshotTimestampMs) && (currentTimestamp - mRequestTakeScreenshotTimestampMs)
<= AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) { <= AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
return false; sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
callback);
return;
} }
mRequestTakeScreenshotTimestampMs = currentTimestamp; mRequestTakeScreenshotTimestampMs = currentTimestamp;
synchronized (mLock) { synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) { if (!hasRightsToCurrentUserLocked()) {
return false; sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
callback);
return;
} }
if (!mSecurityPolicy.canTakeScreenshotLocked(this)) { if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
@@ -998,43 +1004,57 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
} }
if (!mSecurityPolicy.checkAccessibilityAccess(this)) { if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return false; sendScreenshotFailure(
AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
callback);
return;
} }
final Display display = DisplayManagerGlobal.getInstance() final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(displayId); .getRealDisplay(displayId);
if (display == null) { 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(); final long identity = Binder.clearCallingIdentity();
try { try {
mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
final Point displaySize = new Point(); final ScreenshotGraphicBuffer screenshotBuffer = takeScreenshotBuffer(display);
// 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 GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer(); final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
try (HardwareBuffer hardwareBuffer = try (HardwareBuffer hardwareBuffer =
HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) { HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
final ParcelableColorSpace colorSpace = final ParcelableColorSpace colorSpace =
new ParcelableColorSpace(screenshotBuffer.getColorSpace()); new ParcelableColorSpace(screenshotBuffer.getColorSpace());
// Send back the result.
final Bundle payload = new Bundle(); final Bundle payload = new Bundle();
payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
hardwareBuffer); hardwareBuffer);
payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
SystemClock.uptimeMillis()); SystemClock.uptimeMillis());
// Send back the result.
callback.sendResult(payload); callback.sendResult(payload);
hardwareBuffer.close(); hardwareBuffer.close();
} }
@@ -1042,8 +1062,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
} finally { } finally {
Binder.restoreCallingIdentity(identity); 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 @Override

View File

@@ -328,8 +328,6 @@ class UiAutomationManager {
public void onFingerprintGesture(int gesture) {} public void onFingerprintGesture(int gesture) {}
@Override @Override
public boolean takeScreenshot(int displayId, RemoteCallback callback) { public void takeScreenshot(int displayId, RemoteCallback callback) {}
return false;
}
} }
} }

View File

@@ -47,7 +47,6 @@ import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@@ -698,18 +697,8 @@ public class AbstractAccessibilityServiceConnectionTest {
assertThat(result, is(false)); 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) @Test (expected = SecurityException.class)
public void takeScreenshot_throwSecurityException() { public void takeScreenshot_withoutCapability_throwSecurityException() {
// no canTakeScreenshot, should throw security exception. // no canTakeScreenshot, should throw security exception.
when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> { mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> {