Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 45d63533 authored by Jacky Kao's avatar Jacky Kao Committed by Automerger Merge Worker
Browse files

Merge "Supporting multi-display for takeScreenshot()" into rvc-dev am: aa8f5cb6

Change-Id: I03c220f6be59a041015f9959bb7905e7e328bf20
parents 2c587381 aa8f5cb6
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -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);
+9 −0
Original line number Diff line number Diff line
@@ -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.
     *
+41 −45
Original line number Diff line number Diff line
@@ -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,35 +1007,38 @@ 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 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 {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void sendScreenshotSuccess(ScreenshotGraphicBuffer screenshotBuffer,
            RemoteCallback callback) {
        final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
        try (HardwareBuffer hardwareBuffer =
                     HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
@@ -1058,10 +1058,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
            callback.sendResult(payload);
            hardwareBuffer.close();
        }
            }, null).recycleOnUse());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode,
+17 −5
Original line number Diff line number Diff line
@@ -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();
            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
+38 −0
Original line number Diff line number Diff line
@@ -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;