Merge "Supporting multi-display for takeScreenshot()" into rvc-dev am: aa8f5cb671 am: ba452787c7 am: 8baf0cebbe am: f08a1a7d2f

Change-Id: I6be761c26c509a7c7cef216d097ac6f6756b772c
This commit is contained in:
Jacky Kao
2020-04-06 09:58:00 +00:00
committed by Automerger Merge Worker
5 changed files with 105 additions and 60 deletions

View File

@@ -47,7 +47,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -1981,8 +1980,6 @@ public abstract class AccessibilityService extends Service {
* 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}.
* This API only will support {@link Display#DEFAULT_DISPLAY} until {@link SurfaceControl}
* supports non-default displays.
* </p>
*
* @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
@@ -1990,18 +1987,11 @@ 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.
*
* @throws IllegalArgumentException if displayId is not {@link Display#DEFAULT_DISPLAY}.
*/
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");
if (displayId != Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("DisplayId isn't the default display");
}
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance().getConnection(
mConnectionId);

View File

@@ -71,6 +71,15 @@ public abstract class DisplayManagerInternal {
*/
public abstract SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId);
/**
* Take a screenshot without secure layer of the specified display and return a buffer.
*
* @param displayId The display id to take the screenshot of.
* @return The buffer or null if we have failed.
*/
public abstract SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(
int displayId);
/**
* Returns information about the specified logical display.
*

View File

@@ -43,12 +43,10 @@ import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.graphics.GraphicBuffer;
import android.graphics.ParcelableColorSpace;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -66,7 +64,6 @@ import android.util.SparseArray;
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;
@@ -1010,53 +1007,29 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return;
}
final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(displayId);
if (display == null) {
sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
callback);
// Private virtual displays are created by the ap and is not allowed to access by other
// aps. We assume the contents on this display should not be captured.
final DisplayManager displayManager =
(DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
final Display display = displayManager.getDisplay(displayId);
if ((display == null) || (display.getType() == Display.TYPE_VIRTUAL
&& (display.getFlags() & Display.FLAG_PRIVATE) != 0)) {
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 ScreenshotGraphicBuffer screenshotBuffer = takeScreenshotBuffer(display);
final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
try (HardwareBuffer hardwareBuffer =
HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
final ParcelableColorSpace colorSpace =
new ParcelableColorSpace(screenshotBuffer.getColorSpace());
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();
final ScreenshotGraphicBuffer screenshotBuffer = LocalServices
.getService(DisplayManagerInternal.class)
.screenshotWithoutSecureLayer(displayId);
if (screenshotBuffer != null) {
sendScreenshotSuccess(screenshotBuffer, callback);
} else {
sendScreenshotFailure(
AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback);
}
}, null).recycleOnUse());
} finally {
@@ -1064,6 +1037,29 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
}
private void sendScreenshotSuccess(ScreenshotGraphicBuffer screenshotBuffer,
RemoteCallback callback) {
final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
try (HardwareBuffer hardwareBuffer =
HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
final ParcelableColorSpace colorSpace =
new ParcelableColorSpace(screenshotBuffer.getColorSpace());
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();
}
}
private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode,
RemoteCallback callback) {
mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {

View File

@@ -1362,7 +1362,8 @@ public final class DisplayManagerService extends SystemService {
return null;
}
private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) {
private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId,
boolean captureSecureLayer) {
synchronized (mSyncRoot) {
final IBinder token = getDisplayToken(displayId);
if (token == null) {
@@ -1374,9 +1375,15 @@ public final class DisplayManagerService extends SystemService {
}
final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
false /* useIdentityTransform */, 0 /* rotation */);
if (captureSecureLayer) {
return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
false /* useIdentityTransform */, 0 /* rotation */);
} else {
return SurfaceControl.screenshotToBuffer(token, new Rect(),
displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
false /* useIdentityTransform */, 0 /* rotation */);
}
}
}
@@ -2494,7 +2501,12 @@ public final class DisplayManagerService extends SystemService {
@Override
public SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId) {
return screenshotInternal(displayId);
return screenshotInternal(displayId, true);
}
@Override
public SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(int displayId) {
return screenshotInternal(displayId, false);
}
@Override

View File

@@ -16,7 +16,10 @@
package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY;
import static android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION;
import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES;
import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
@@ -69,6 +72,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -93,6 +97,7 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -162,6 +167,7 @@ public class AbstractAccessibilityServiceConnectionTest {
@Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
@Mock private FingerprintGestureDispatcher mMockFingerprintGestureDispatcher;
@Mock private MagnificationController mMockMagnificationController;
@Mock private RemoteCallback.OnResultListener mMockListener;
@Before
public void setup() {
@@ -705,6 +711,38 @@ public class AbstractAccessibilityServiceConnectionTest {
}));
}
@Test
public void takeScreenshot_NoA11yAccess_returnErrorCode() throws InterruptedException {
// no checkAccessibilityAccess, should return error code.
when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY,
new RemoteCallback(mMockListener));
mHandler.sendLastMessage();
verify(mMockListener).onResult(Mockito.argThat(
bundle -> ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS
== bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS)));
}
@Test
public void takeScreenshot_invalidDisplay_returnErrorCode() throws InterruptedException {
when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true);
final DisplayManager displayManager = new DisplayManager(mMockContext);
when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(displayManager);
mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY + 1,
new RemoteCallback(mMockListener));
mHandler.sendLastMessage();
verify(mMockListener).onResult(Mockito.argThat(
bundle -> ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
== bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS)));
}
private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
serviceInfo.eventTypes = eventType;