Merge "Extracts image acquisition to a separate class" into tm-qpr-dev am: a97e6db869 am: 0a7975e5b0
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19415901
Change-Id: Id5407a534857088b2b516728a797b980455aaa55
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
new file mode 100644
index 0000000..39f35a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
@@ -0,0 +1,26 @@
+/*
+ * 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?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
new file mode 100644
index 0000000..258c436
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * 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)
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 82de389..69ee8e8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -57,14 +57,12 @@
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.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 @@
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 @@
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
+ ImageCapture imageCapture,
@Main Executor mainExecutor,
ScrollCaptureController scrollCaptureController,
LongScreenshotData longScreenshotHolder,
@@ -308,6 +307,7 @@
mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
+ mImageCapture = imageCapture;
mMainExecutor = mainExecutor;
mScrollCaptureController = scrollCaptureController;
mLongScreenshotHolder = longScreenshotHolder;
@@ -531,7 +531,7 @@
// 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 @@
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 @@
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,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 91ef3c3..3e442587 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -18,6 +18,8 @@
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 @@
@ClassKey(TakeScreenshotService.class)
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+ @Binds
+ public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
new file mode 100644
index 0000000..ce3f20d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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