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

Commit 7f7ebbe4 authored by Chavi Weingarten's avatar Chavi Weingarten Committed by Android (Google) Code Review
Browse files

Merge "Replace screenshots using internal displayToken with new API"

parents 9cd8a90e 5ae620bd
Loading
Loading
Loading
Loading
+16 −9
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.app;

import static android.view.Display.DEFAULT_DISPLAY;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
@@ -35,6 +37,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.Log;
import android.util.Pair;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -46,6 +49,8 @@ import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.CaptureArgs;
import android.window.ScreenCapture.ScreenCaptureListener;

import libcore.io.IoUtils;

@@ -218,20 +223,22 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
        }
        final long identity = Binder.clearCallingIdentity();
        try {
            int width = crop.width();
            int height = crop.height();
            final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
            final ScreenCapture.DisplayCaptureArgs captureArgs =
                    new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
            final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
                    .setSourceCrop(crop)
                            .setSize(width, height)
                    .build();
            Pair<ScreenCaptureListener, ScreenCapture.ScreenshotSync> syncScreenCapture =
                    ScreenCapture.createSyncCaptureListener();
            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs,
                    syncScreenCapture.first);
            final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                    ScreenCapture.captureDisplay(captureArgs);
                    syncScreenCapture.second.get();
            return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
        } catch (RemoteException re) {
            re.rethrowAsRuntimeException();
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        return null;
    }

    @Nullable
+17 −8
Original line number Diff line number Diff line
@@ -16,11 +16,17 @@

package com.android.shell;

import static android.view.Display.DEFAULT_DISPLAY;

import android.graphics.Bitmap;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControl;
import android.util.Pair;
import android.view.WindowManagerGlobal;
import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenCaptureListener;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import android.window.ScreenCapture.ScreenshotSync;

/**
 * Helper class used to take screenshots.
@@ -40,12 +46,15 @@ final class Screenshooter {
    static Bitmap takeScreenshot() {
        Log.d(TAG, "Taking fullscreen screenshot");
        // Take the screenshot
        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
        final ScreenCapture.DisplayCaptureArgs captureArgs =
                new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
                        .build();
        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                ScreenCapture.captureDisplay(captureArgs);
        final Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture =
                ScreenCapture.createSyncCaptureListener();
        try {
            WindowManagerGlobal.getWindowManagerService().captureDisplay(DEFAULT_DISPLAY, null,
                    syncScreenCapture.first);
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
        final ScreenshotHardwareBuffer screenshotBuffer = syncScreenCapture.second.get();
        final Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
        if (screenShot == null) {
            Log.e(TAG, "Failed to take fullscreen screenshot");
+9 −45
Original line number Diff line number Diff line
@@ -19,15 +19,9 @@ package com.android.systemui.screenshot
import android.app.IActivityTaskManager
import android.graphics.Bitmap
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.IBinder
import android.util.Log
import android.view.DisplayAddress
import android.view.SurfaceControl
import android.view.IWindowManager
import android.window.ScreenCapture
import android.window.ScreenCapture.DisplayCaptureArgs
import android.window.ScreenCapture.ScreenshotHardwareBuffer
import androidx.annotation.VisibleForTesting
import android.window.ScreenCapture.CaptureArgs
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
@@ -38,18 +32,18 @@ private const val TAG = "ImageCaptureImpl"

@SysUISingleton
open class ImageCaptureImpl @Inject constructor(
    private val displayManager: DisplayManager,
    private val windowManager: IWindowManager,
    private val atmService: IActivityTaskManager,
    @Background private val bgContext: CoroutineDispatcher
) : ImageCapture {

    override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
        val width = crop?.width() ?: 0
        val height = crop?.height() ?: 0
        val sourceCrop = crop ?: Rect()
        val displayToken = physicalDisplayToken(displayId) ?: return null
        val buffer = captureDisplay(displayToken, width, height, sourceCrop)

        val captureArgs = CaptureArgs.Builder()
            .setSourceCrop(crop)
            .build()
        val syncScreenCapture = ScreenCapture.createSyncCaptureListener()
        windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture.first)
        val buffer = syncScreenCapture.second.get()
        return buffer?.asBitmap()
    }

@@ -57,34 +51,4 @@ open class ImageCaptureImpl @Inject constructor(
        val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null
        return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
    }

    @VisibleForTesting
    open fun physicalDisplayToken(displayId: Int): IBinder? {
        val display = displayManager.getDisplay(displayId)
        if (display == null) {
            Log.e(TAG, "No display with id: $displayId")
            return null
        }
        val address = display.address
        if (address !is DisplayAddress.Physical) {
            Log.e(TAG, "Display does not have a physical address: $display")
            return null
        }
        return SurfaceControl.getPhysicalDisplayToken(address.physicalDisplayId)
    }

    @VisibleForTesting
    open fun captureDisplay(
        displayToken: IBinder,
        width: Int,
        height: Int,
        crop: Rect
    ): ScreenshotHardwareBuffer? {
        val captureArgs =
            DisplayCaptureArgs.Builder(displayToken)
                .setSize(width, height)
                .setSourceCrop(crop)
                .build()
        return ScreenCapture.captureDisplay(captureArgs)
    }
}
+0 −95
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.screenshot

import android.app.IActivityTaskManager
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.Binder
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
import android.window.ScreenCapture.ScreenshotHardwareBuffer
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Test the logic within ImageCaptureImpl
 */
@RunWith(AndroidTestingRunner::class)
class ImageCaptureImplTest : SysuiTestCase() {
    private val displayManager = mock<DisplayManager>()
    private val atmService = mock<IActivityTaskManager>()
    private val capture = TestableImageCaptureImpl(
        displayManager,
        atmService,
        Dispatchers.Unconfined)

    @Test
    fun captureDisplayWithCrop() {
        capture.captureDisplay(Display.DEFAULT_DISPLAY, Rect(1, 2, 3, 4))
        assertThat(capture.token).isNotNull()
        assertThat(capture.width!!).isEqualTo(2)
        assertThat(capture.height!!).isEqualTo(2)
        assertThat(capture.crop!!).isEqualTo(Rect(1, 2, 3, 4))
    }

    @Test
    fun captureDisplayWithNullCrop() {
        capture.captureDisplay(Display.DEFAULT_DISPLAY, null)
        assertThat(capture.token).isNotNull()
        assertThat(capture.width!!).isEqualTo(0)
        assertThat(capture.height!!).isEqualTo(0)
        assertThat(capture.crop!!).isEqualTo(Rect())
    }

    class TestableImageCaptureImpl(
        displayManager: DisplayManager,
        atmService: IActivityTaskManager,
        bgDispatcher: CoroutineDispatcher
    ) :
        ImageCaptureImpl(displayManager, atmService, bgDispatcher) {

        var token: IBinder? = null
        var width: Int? = null
        var height: Int? = null
        var crop: Rect? = null

        override fun physicalDisplayToken(displayId: Int): IBinder {
            return Binder()
        }

        override fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect):
                ScreenshotHardwareBuffer {
            this.token = displayToken
            this.width = width
            this.height = height
            this.crop = crop
            return ScreenshotHardwareBuffer(
                null,
                null,
                false,
                false
            )
        }
    }
}
+12 −12
Original line number Diff line number Diff line
@@ -196,6 +196,7 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
import android.util.IntArray;
import android.util.Pair;
import android.util.RotationUtils;
import android.util.Size;
import android.util.Slog;
@@ -4890,20 +4891,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            return null;
        }

        final ScreenRotationAnimation screenRotationAnimation =
                mWmService.mRoot.getDisplayContent(DEFAULT_DISPLAY).getRotationAnimation();
        final boolean inRotation = screenRotationAnimation != null &&
                screenRotationAnimation.isAnimating();
        if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
        Pair<ScreenCapture.ScreenCaptureListener, ScreenCapture.ScreenshotSync> syncScreenCapture =
                ScreenCapture.createSyncCaptureListener();

        getBounds(mTmpRect);
        mTmpRect.offsetTo(0, 0);
        ScreenCapture.LayerCaptureArgs args =
                new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
                        .setSourceCrop(mTmpRect).build();

        ScreenCapture.captureLayers(args, syncScreenCapture.first);

        // Send invalid rect and no width and height since it will screenshot the entire display.
        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
        final ScreenCapture.DisplayCaptureArgs captureArgs =
                new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
                        .setUseIdentityTransform(inRotation)
                        .build();
        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                ScreenCapture.captureDisplay(captureArgs);
                syncScreenCapture.second.get();
        final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
        if (bitmap == null) {
            Slog.w(TAG_WM, "Failed to take screenshot");