Merge "Extracts image acquisition to a separate class" into tm-qpr-dev
This commit is contained in:
@@ -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?
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -57,14 +57,12 @@ import android.media.AudioSystem;
|
|||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.DisplayAddress;
|
|
||||||
import android.view.IRemoteAnimationFinishedCallback;
|
import android.view.IRemoteAnimationFinishedCallback;
|
||||||
import android.view.IRemoteAnimationRunner;
|
import android.view.IRemoteAnimationRunner;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -72,7 +70,6 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.RemoteAnimationAdapter;
|
import android.view.RemoteAnimationAdapter;
|
||||||
import android.view.RemoteAnimationTarget;
|
import android.view.RemoteAnimationTarget;
|
||||||
import android.view.ScrollCaptureResponse;
|
import android.view.ScrollCaptureResponse;
|
||||||
import android.view.SurfaceControl;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewRootImpl;
|
import android.view.ViewRootImpl;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
@@ -249,6 +246,7 @@ public class ScreenshotController {
|
|||||||
private final ScreenshotSmartActions mScreenshotSmartActions;
|
private final ScreenshotSmartActions mScreenshotSmartActions;
|
||||||
private final UiEventLogger mUiEventLogger;
|
private final UiEventLogger mUiEventLogger;
|
||||||
private final ImageExporter mImageExporter;
|
private final ImageExporter mImageExporter;
|
||||||
|
private final ImageCapture mImageCapture;
|
||||||
private final Executor mMainExecutor;
|
private final Executor mMainExecutor;
|
||||||
private final ExecutorService mBgExecutor;
|
private final ExecutorService mBgExecutor;
|
||||||
private final BroadcastSender mBroadcastSender;
|
private final BroadcastSender mBroadcastSender;
|
||||||
@@ -295,6 +293,7 @@ public class ScreenshotController {
|
|||||||
ScrollCaptureClient scrollCaptureClient,
|
ScrollCaptureClient scrollCaptureClient,
|
||||||
UiEventLogger uiEventLogger,
|
UiEventLogger uiEventLogger,
|
||||||
ImageExporter imageExporter,
|
ImageExporter imageExporter,
|
||||||
|
ImageCapture imageCapture,
|
||||||
@Main Executor mainExecutor,
|
@Main Executor mainExecutor,
|
||||||
ScrollCaptureController scrollCaptureController,
|
ScrollCaptureController scrollCaptureController,
|
||||||
LongScreenshotData longScreenshotHolder,
|
LongScreenshotData longScreenshotHolder,
|
||||||
@@ -308,6 +307,7 @@ public class ScreenshotController {
|
|||||||
mScrollCaptureClient = scrollCaptureClient;
|
mScrollCaptureClient = scrollCaptureClient;
|
||||||
mUiEventLogger = uiEventLogger;
|
mUiEventLogger = uiEventLogger;
|
||||||
mImageExporter = imageExporter;
|
mImageExporter = imageExporter;
|
||||||
|
mImageCapture = imageCapture;
|
||||||
mMainExecutor = mainExecutor;
|
mMainExecutor = mainExecutor;
|
||||||
mScrollCaptureController = scrollCaptureController;
|
mScrollCaptureController = scrollCaptureController;
|
||||||
mLongScreenshotHolder = longScreenshotHolder;
|
mLongScreenshotHolder = longScreenshotHolder;
|
||||||
@@ -531,7 +531,7 @@ public class ScreenshotController {
|
|||||||
|
|
||||||
// copy the input Rect, since SurfaceControl.screenshot can mutate it
|
// copy the input Rect, since SurfaceControl.screenshot can mutate it
|
||||||
Rect screenRect = new Rect(crop);
|
Rect screenRect = new Rect(crop);
|
||||||
Bitmap screenshot = captureScreenshot(crop);
|
Bitmap screenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY, crop);
|
||||||
|
|
||||||
if (screenshot == null) {
|
if (screenshot == null) {
|
||||||
Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
|
Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
|
||||||
@@ -549,32 +549,6 @@ public class ScreenshotController {
|
|||||||
ClipboardOverlayController.SELF_PERMISSION);
|
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,
|
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
|
||||||
Insets screenInsets, ComponentName topComponent, boolean showFlash) {
|
Insets screenInsets, ComponentName topComponent, boolean showFlash) {
|
||||||
withWindowAttached(() ->
|
withWindowAttached(() ->
|
||||||
@@ -720,7 +694,7 @@ public class ScreenshotController {
|
|||||||
mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
|
mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
|
||||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||||
getDefaultDisplay().getRealMetrics(displayMetrics);
|
getDefaultDisplay().getRealMetrics(displayMetrics);
|
||||||
Bitmap newScreenshot = captureScreenshot(
|
Bitmap newScreenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY,
|
||||||
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
|
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
|
||||||
|
|
||||||
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
|
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package com.android.systemui.screenshot.dagger;
|
|||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
|
||||||
|
import com.android.systemui.screenshot.ImageCapture;
|
||||||
|
import com.android.systemui.screenshot.ImageCaptureImpl;
|
||||||
import com.android.systemui.screenshot.TakeScreenshotService;
|
import com.android.systemui.screenshot.TakeScreenshotService;
|
||||||
|
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
@@ -37,4 +39,6 @@ public abstract class ScreenshotModule {
|
|||||||
@ClassKey(TakeScreenshotService.class)
|
@ClassKey(TakeScreenshotService.class)
|
||||||
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
|
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user