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

Commit a97e6db8 authored by Mark Renouf's avatar Mark Renouf Committed by Android (Google) Code Review
Browse files

Merge "Extracts image acquisition to a separate class" into tm-qpr-dev

parents 36931daf afbac470
Loading
Loading
Loading
Loading
+26 −0
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.graphics.Bitmap
import android.graphics.Rect

interface ImageCapture {

    fun captureDisplay(displayId: Int, crop: Rect? = null): Bitmap?

    fun captureTask(taskId: Int): Bitmap?
}
+78 −0
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.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.SurfaceControl.DisplayCaptureArgs
import android.view.SurfaceControl.ScreenshotHardwareBuffer
import androidx.annotation.VisibleForTesting
import javax.inject.Inject

private const val TAG = "ImageCaptureImpl"

open class ImageCaptureImpl @Inject constructor(
    private val displayManager: DisplayManager,
    private val atmService: IActivityTaskManager
) : 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)

        return buffer?.asBitmap()
    }

    override fun captureTask(taskId: Int): Bitmap? {
        val snapshot = atmService.takeTaskSnapshot(taskId)
        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 SurfaceControl.captureDisplay(captureArgs)
    }

}
+5 −31
Original line number Diff line number Diff line
@@ -57,14 +57,12 @@ import android.media.AudioSystem;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
@@ -72,7 +70,6 @@ import android.view.LayoutInflater;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
@@ -249,6 +246,7 @@ public class ScreenshotController {
    private final ScreenshotSmartActions mScreenshotSmartActions;
    private final UiEventLogger mUiEventLogger;
    private final ImageExporter mImageExporter;
    private final ImageCapture mImageCapture;
    private final Executor mMainExecutor;
    private final ExecutorService mBgExecutor;
    private final BroadcastSender mBroadcastSender;
@@ -295,6 +293,7 @@ public class ScreenshotController {
            ScrollCaptureClient scrollCaptureClient,
            UiEventLogger uiEventLogger,
            ImageExporter imageExporter,
            ImageCapture imageCapture,
            @Main Executor mainExecutor,
            ScrollCaptureController scrollCaptureController,
            LongScreenshotData longScreenshotHolder,
@@ -308,6 +307,7 @@ public class ScreenshotController {
        mScrollCaptureClient = scrollCaptureClient;
        mUiEventLogger = uiEventLogger;
        mImageExporter = imageExporter;
        mImageCapture = imageCapture;
        mMainExecutor = mainExecutor;
        mScrollCaptureController = scrollCaptureController;
        mLongScreenshotHolder = longScreenshotHolder;
@@ -531,7 +531,7 @@ public class ScreenshotController {

        // copy the input Rect, since SurfaceControl.screenshot can mutate it
        Rect screenRect = new Rect(crop);
        Bitmap screenshot = captureScreenshot(crop);
        Bitmap screenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY, crop);

        if (screenshot == null) {
            Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
@@ -549,32 +549,6 @@ public class ScreenshotController {
                ClipboardOverlayController.SELF_PERMISSION);
    }

    private Bitmap captureScreenshot(Rect crop) {
        int width = crop.width();
        int height = crop.height();
        Bitmap screenshot = null;
        final Display display = getDefaultDisplay();
        final DisplayAddress address = display.getAddress();
        if (!(address instanceof DisplayAddress.Physical)) {
            Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "
                    + display);
        } else {
            final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;

            final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
                    physicalAddress.getPhysicalDisplayId());
            final SurfaceControl.DisplayCaptureArgs captureArgs =
                    new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
                            .setSourceCrop(crop)
                            .setSize(width, height)
                            .build();
            final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                    SurfaceControl.captureDisplay(captureArgs);
            screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
        }
        return screenshot;
    }

    private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
            Insets screenInsets, ComponentName topComponent, boolean showFlash) {
        withWindowAttached(() ->
@@ -720,7 +694,7 @@ public class ScreenshotController {
            mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
                DisplayMetrics displayMetrics = new DisplayMetrics();
                getDefaultDisplay().getRealMetrics(displayMetrics);
                Bitmap newScreenshot = captureScreenshot(
                Bitmap newScreenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY,
                        new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));

                mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+4 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.screenshot.dagger;

import android.app.Service;

import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.TakeScreenshotService;

import dagger.Binds;
@@ -37,4 +39,6 @@ public abstract class ScreenshotModule {
    @ClassKey(TakeScreenshotService.class)
    public abstract Service bindTakeScreenshotService(TakeScreenshotService service);

    @Binds
    public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
}
+84 −0
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.view.SurfaceControl.ScreenshotHardwareBuffer
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
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)

    @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
    ) :
        ImageCaptureImpl(displayManager, atmService) {

        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)
        }
    }
}
 No newline at end of file