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

Commit d1a0f4b1 authored by Mark Renouf's avatar Mark Renouf
Browse files

Screenshot request processor

Inserts a preprocessing stage to screenshot request handling
to contain support for new features. This new branch is
guarded by the flag 'SCREENSHOT_REQUEST_PROCESSOR'.

To enable:

 adb shell cmd statusbar flag 1300 on

No other functional changes should be observed.

Bug: 231957192
Bug: 200294244
Test: atest RequestProcessorTest
Test: manual; enable flags, take screenshots

Change-Id: I2fb26f9bf30513199b7e6d5cb97f6ccf99fcc0eb
parent e3022817
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -214,6 +214,10 @@ public class Flags {
    public static final BooleanFlag NEW_BACK_AFFORDANCE =
            new BooleanFlag(1203, false /* default */, true /* teamfood */);

    // 1300 - screenshots

    public static final BooleanFlag SCREENSHOT_REQUEST_PROCESSOR = new BooleanFlag(1300, false);

    // Pay no attention to the reflection behind the curtain.
    // ========================== Curtain ==========================
    // |                                                           |
+68 −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.net.Uri
import android.util.Log
import android.view.WindowManager.ScreenshotType
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import java.util.function.Consumer
import javax.inject.Inject

/**
 * Processes a screenshot request sent from {@link ScreenshotHelper}.
 */
@SysUISingleton
internal class RequestProcessor @Inject constructor(
    private val controller: ScreenshotController,
) {
    fun processRequest(
        @ScreenshotType type: Int,
        onSavedListener: Consumer<Uri>,
        request: ScreenshotRequest,
        callback: RequestCallback
    ) {

        if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
            val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)

            controller.handleImageAsScreenshot(
                image, request.boundsInScreen, request.insets,
                request.taskId, request.userId, request.topComponent, onSavedListener, callback
            )
            return
        }

        when (type) {
            TAKE_SCREENSHOT_FULLSCREEN ->
                controller.takeScreenshotFullscreen(null, onSavedListener, callback)
            TAKE_SCREENSHOT_SELECTED_REGION ->
                controller.takeScreenshotPartial(null, onSavedListener, callback)
            else -> Log.w(TAG, "Invalid screenshot option: $type")
        }
    }

    companion object {
        const val TAG: String = "RequestProcessor"
    }
}
+16 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;

import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
@@ -57,6 +58,8 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagListenable.FlagEvent;

import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -75,6 +78,8 @@ public class TakeScreenshotService extends Service {
    private final Handler mHandler;
    private final Context mContext;
    private final @Background Executor mBgExecutor;
    private final RequestProcessor mProcessor;
    private final FeatureFlags mFeatureFlags;

    private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
        @Override
@@ -104,7 +109,8 @@ public class TakeScreenshotService extends Service {
    public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
            DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger,
            ScreenshotNotificationsController notificationsController, Context context,
            @Background Executor bgExecutor) {
            @Background Executor bgExecutor, FeatureFlags featureFlags,
            RequestProcessor processor) {
        if (DEBUG_SERVICE) {
            Log.d(TAG, "new " + this);
        }
@@ -116,6 +122,9 @@ public class TakeScreenshotService extends Service {
        mNotificationsController = notificationsController;
        mContext = context;
        mBgExecutor = bgExecutor;
        mFeatureFlags = featureFlags;
        mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
        mProcessor = processor;
    }

    @Override
@@ -218,6 +227,12 @@ public class TakeScreenshotService extends Service {
        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,
                topComponent == null ? "" : topComponent.getPackageName());

        if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
            Log.d(TAG, "handleMessage: Using request processor");
            mProcessor.processRequest(msg.what, uriConsumer, screenshotRequest, requestCallback);
            return true;
        }

        switch (msg.what) {
            case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                if (DEBUG_SERVICE) {
+106 −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.content.ComponentName
import android.graphics.Bitmap
import android.graphics.ColorSpace
import android.graphics.Insets
import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.net.Uri
import android.view.WindowManager
import android.view.WindowManager.ScreenshotSource
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Test
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
import org.mockito.Mockito.isNull

class RequestProcessorTest {
    private val controller = mock<ScreenshotController>()
    private val bitmapCaptor = argumentCaptor<Bitmap>()

    @Test
    fun testFullScreenshot() {
        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
        val onSavedListener = mock<Consumer<Uri>>()
        val callback = mock<RequestCallback>()
        val processor = RequestProcessor(controller)

        processor.processRequest(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, onSavedListener,
            request, callback)

        verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
            eq(onSavedListener), eq(callback))
    }

    @Test
    fun testSelectedRegionScreenshot() {
        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
        val onSavedListener = mock<Consumer<Uri>>()
        val callback = mock<RequestCallback>()
        val processor = RequestProcessor(controller)

        processor.processRequest(WindowManager.TAKE_SCREENSHOT_SELECTED_REGION, onSavedListener,
            request, callback)

        verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
            eq(onSavedListener), eq(callback))
    }

    @Test
    fun testProvidedImageScreenshot() {
        val taskId = 1111
        val userId = 2222
        val bounds = Rect(50, 50, 150, 150)
        val topComponent = ComponentName("test", "test")
        val processor = RequestProcessor(controller)

        val buffer = HardwareBuffer.create(100, 100, HardwareBuffer.RGBA_8888, 1,
            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
        val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)

        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_OTHER, bitmapBundle,
            bounds, Insets.NONE, taskId, userId, topComponent)

        val onSavedListener = mock<Consumer<Uri>>()
        val callback = mock<RequestCallback>()

        processor.processRequest(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, onSavedListener,
            request, callback)

        verify(controller).handleImageAsScreenshot(
            bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
            eq(topComponent), eq(onSavedListener), eq(callback)
        )

        assertThat(bitmapCaptor.value.equalsHardwareBitmap(bitmap)).isTrue()
    }

    private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
        return bitmap.hardwareBuffer == this.hardwareBuffer &&
                bitmap.colorSpace == this.colorSpace
    }
}