Loading packages/SystemUI/src/com/android/systemui/flags/Flags.java +4 −0 Original line number Diff line number Diff line Loading @@ -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 ========================== // | | Loading packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt 0 → 100644 +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" } } packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +16 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); } Loading @@ -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 Loading Loading @@ -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) { Loading packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt 0 → 100644 +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 } } Loading
packages/SystemUI/src/com/android/systemui/flags/Flags.java +4 −0 Original line number Diff line number Diff line Loading @@ -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 ========================== // | | Loading
packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt 0 → 100644 +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" } }
packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +16 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); } Loading @@ -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 Loading Loading @@ -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) { Loading
packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt 0 → 100644 +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 } }