diff options
4 files changed, 194 insertions, 1 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 7e88946cb8c3..d4799883129e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -217,6 +217,10 @@ public class Flags { public static final BooleanFlag NEW_BACK_AFFORDANCE = new BooleanFlag(1203, false /* default */, false /* teamfood */); + // 1300 - screenshots + + public static final BooleanFlag SCREENSHOT_REQUEST_PROCESSOR = new BooleanFlag(1300, false); + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt new file mode 100644 index 000000000000..beb54c85e021 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -0,0 +1,68 @@ +/* + * 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" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 32d82037efb9..f1f0223632b7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -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) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt new file mode 100644 index 000000000000..002f23a9ed6d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -0,0 +1,106 @@ +/* + * 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 + } +} |