summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt201
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt303
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt99
9 files changed, 706 insertions, 76 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index c0d807a78e90..98f2fee13de2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -28,8 +28,18 @@ import kotlinx.coroutines.launch
import java.util.function.Consumer
import javax.inject.Inject
+/** Processes a screenshot request sent from [ScreenshotHelper]. */
+interface ScreenshotRequestProcessor {
+ /**
+ * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
+ *
+ * @param screenshot the screenshot to process
+ */
+ suspend fun process(screenshot: ScreenshotData): ScreenshotData
+}
+
/**
- * Processes a screenshot request sent from {@link ScreenshotHelper}.
+ * Implementation of [ScreenshotRequestProcessor]
*/
@SysUISingleton
class RequestProcessor @Inject constructor(
@@ -38,7 +48,7 @@ class RequestProcessor @Inject constructor(
private val flags: FeatureFlags,
/** For the Java Async version, to invoke the callback. */
@Application private val mainScope: CoroutineScope
-) {
+) : ScreenshotRequestProcessor {
/**
* Inspects the incoming request, returning a potentially modified request depending on policy.
*
@@ -57,7 +67,6 @@ class RequestProcessor @Inject constructor(
// regardless of the managed profile status.
if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
-
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
Log.d(TAG, "findPrimaryContent: $info")
@@ -99,12 +108,7 @@ class RequestProcessor @Inject constructor(
}
}
- /**
- * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
- *
- * @param screenshot the screenshot to process
- */
- suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+ override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
var result = screenshot
// Apply work profile screenshots policy:
@@ -116,7 +120,7 @@ class RequestProcessor @Inject constructor(
// regardless of the managed profile status.
if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+ val info = policy.findPrimaryContent(screenshot.displayId)
Log.d(TAG, "findPrimaryContent: $info")
result.taskId = info.taskId
result.topComponent = info.component
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b59106efb769..cf782b7d93c6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -100,11 +100,14 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.io.File;
import java.util.List;
import java.util.concurrent.CancellationException;
@@ -118,7 +121,6 @@ import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;
-import javax.inject.Inject;
/**
* Controls the state and flow for screenshots.
@@ -275,7 +277,7 @@ public class ScreenshotController {
private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
- private final DisplayTracker mDisplayTracker;
+ private final int mDisplayId;
private final ScrollCaptureController mScrollCaptureController;
private final LongScreenshotData mLongScreenshotHolder;
private final boolean mIsLowRamDevice;
@@ -314,7 +316,8 @@ public class ScreenshotController {
| ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_ASSETS_PATHS);
- @Inject
+
+ @AssistedInject
ScreenshotController(
Context context,
FeatureFlags flags,
@@ -335,7 +338,7 @@ public class ScreenshotController {
UserManager userManager,
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
- DisplayTracker displayTracker
+ @Assisted int displayId
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -360,9 +363,9 @@ public class ScreenshotController {
dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
});
+ mDisplayId = displayId;
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
- mDisplayTracker = displayTracker;
- final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+ final Context displayContext = context.createDisplayContext(getDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
@@ -406,7 +409,7 @@ public class ScreenshotController {
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
Rect bounds = getFullScreenRect();
screenshot.setBitmap(
- mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(), bounds));
+ mImageCapture.captureDisplay(mDisplayId, bounds));
screenshot.setScreenBounds(bounds);
}
@@ -638,7 +641,7 @@ public class ScreenshotController {
setWindowFocusable(false);
}
}, mActionExecutor, mFlags);
- mScreenshotView.setDefaultDisplay(mDisplayTracker.getDefaultDisplayId());
+ mScreenshotView.setDefaultDisplay(mDisplayId);
mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
@@ -727,8 +730,8 @@ public class ScreenshotController {
if (mLastScrollCaptureRequest != null) {
mLastScrollCaptureRequest.cancel(true);
}
- final ListenableFuture<ScrollCaptureResponse> future =
- mScrollCaptureClient.request(mDisplayTracker.getDefaultDisplayId());
+ final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(
+ mDisplayId);
mLastScrollCaptureRequest = future;
mLastScrollCaptureRequest.addListener(() ->
onScrollCaptureResponseReady(future, owner), mMainExecutor);
@@ -758,9 +761,8 @@ public class ScreenshotController {
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
- getDefaultDisplay().getRealMetrics(displayMetrics);
- Bitmap newScreenshot = mImageCapture.captureDisplay(
- mDisplayTracker.getDefaultDisplayId(),
+ getDisplay().getRealMetrics(displayMetrics);
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
@@ -825,7 +827,7 @@ public class ScreenshotController {
try {
WindowManagerGlobal.getWindowManagerService()
.overridePendingAppTransitionRemote(runner,
- mDisplayTracker.getDefaultDisplayId());
+ mDisplayId);
} catch (Exception e) {
Log.e(TAG, "Error overriding screenshot app transition", e);
}
@@ -1160,8 +1162,8 @@ public class ScreenshotController {
}
}
- private Display getDefaultDisplay() {
- return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
+ private Display getDisplay() {
+ return mDisplayManager.getDisplay(mDisplayId);
}
private boolean allowLongScreenshots() {
@@ -1170,7 +1172,7 @@ public class ScreenshotController {
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
- getDefaultDisplay().getRealMetrics(displayMetrics);
+ getDisplay().getRealMetrics(displayMetrics);
return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
}
@@ -1229,4 +1231,11 @@ public class ScreenshotController {
};
}
}
+
+ /** Injectable factory to create screenshot controller instances for a specific display. */
+ @AssistedFactory
+ public interface Factory {
+ /** Creates an instance of the controller for that specific displayId. */
+ ScreenshotController create(int displayId);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index e9be88a59990..92e933a9557b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -6,12 +6,13 @@ import android.graphics.Insets
import android.graphics.Rect
import android.net.Uri
import android.os.UserHandle
+import android.view.Display
import android.view.WindowManager.ScreenshotSource
import android.view.WindowManager.ScreenshotType
import androidx.annotation.VisibleForTesting
import com.android.internal.util.ScreenshotRequest
-/** ScreenshotData represents the current state of a single screenshot being acquired. */
+/** [ScreenshotData] represents the current state of a single screenshot being acquired. */
data class ScreenshotData(
@ScreenshotType var type: Int,
@ScreenshotSource var source: Int,
@@ -23,6 +24,7 @@ data class ScreenshotData(
var taskId: Int,
var insets: Insets,
var bitmap: Bitmap?,
+ var displayId: Int,
/** App-provided URL representing the content the user was looking at in the screenshot. */
var contextUrl: Uri? = null,
) {
@@ -31,22 +33,31 @@ data class ScreenshotData(
companion object {
@JvmStatic
- fun fromRequest(request: ScreenshotRequest): ScreenshotData {
- return ScreenshotData(
- request.type,
- request.source,
- if (request.userId >= 0) UserHandle.of(request.userId) else null,
- request.topComponent,
- request.boundsInScreen,
- request.taskId,
- request.insets,
- request.bitmap,
+ fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) =
+ ScreenshotData(
+ type = request.type,
+ source = request.source,
+ userHandle = if (request.userId >= 0) UserHandle.of(request.userId) else null,
+ topComponent = request.topComponent,
+ screenBounds = request.boundsInScreen,
+ taskId = request.taskId,
+ insets = request.insets,
+ bitmap = request.bitmap,
+ displayId = displayId,
)
- }
@VisibleForTesting
- fun forTesting(): ScreenshotData {
- return ScreenshotData(0, 0, null, null, null, 0, Insets.NONE, null)
- }
+ fun forTesting() =
+ ScreenshotData(
+ type = 0,
+ source = 0,
+ userHandle = null,
+ topComponent = null,
+ screenBounds = null,
+ taskId = 0,
+ insets = Insets.NONE,
+ bitmap = null,
+ displayId = Display.DEFAULT_DISPLAY,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
new file mode 100644
index 000000000000..6c886fcd9c8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -0,0 +1,201 @@
+package com.android.systemui.screenshot
+
+import android.net.Uri
+import android.os.Trace
+import android.util.Log
+import android.view.Display
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.util.ScreenshotRequest
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
+ * result.
+ *
+ * Captures a screenshot for each [Display] available.
+ */
+@SysUISingleton
+class TakeScreenshotExecutor
+@Inject
+constructor(
+ private val screenshotControllerFactory: ScreenshotController.Factory,
+ displayRepository: DisplayRepository,
+ @Application private val mainScope: CoroutineScope,
+ private val screenshotRequestProcessor: ScreenshotRequestProcessor,
+ private val uiEventLogger: UiEventLogger
+) {
+
+ private lateinit var displays: StateFlow<Set<Display>>
+ private val displaysCollectionJob: Job =
+ mainScope.launch {
+ displays = displayRepository.displays.stateIn(this, SharingStarted.Eagerly, emptySet())
+ }
+
+ private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+
+ /**
+ * Executes the [ScreenshotRequest].
+ *
+ * [onSaved] is invoked only on the default display result. [RequestCallback.onFinish] is
+ * invoked only when both screenshot UIs are removed.
+ */
+ suspend fun executeScreenshots(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: (Uri) -> Unit,
+ requestCallback: RequestCallback
+ ) {
+ val displayIds = getDisplaysToScreenshot()
+ val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
+ screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
+ dispatchToController(
+ screenshotData = screenshotData,
+ onSaved =
+ if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> },
+ callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId)
+ )
+ }
+ }
+
+ /** Creates a [ScreenshotData] for each display. */
+ private suspend fun ScreenshotRequest.oneForEachDisplay(
+ displayIds: List<Int>
+ ): List<ScreenshotData> {
+ return displayIds
+ .map { displayId -> ScreenshotData.fromRequest(this, displayId) }
+ .map { screenshotData: ScreenshotData ->
+ screenshotRequestProcessor.process(screenshotData)
+ }
+ }
+
+ private fun dispatchToController(
+ screenshotData: ScreenshotData,
+ onSaved: (Uri) -> Unit,
+ callback: RequestCallback
+ ) {
+ uiEventLogger.log(
+ ScreenshotEvent.getScreenshotSource(screenshotData.source),
+ 0,
+ screenshotData.packageNameString
+ )
+ Log.d(TAG, "Screenshot request: $screenshotData")
+ getScreenshotController(screenshotData.displayId)
+ .handleScreenshot(screenshotData, onSaved, callback)
+ }
+
+ private fun getDisplaysToScreenshot(): List<Int> {
+ return displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+ }
+
+ /**
+ * Propagates the close system dialog signal to all controllers.
+ *
+ * TODO(b/295143676): Move the receiver in this class once the flag is flipped.
+ */
+ fun onCloseSystemDialogsReceived() {
+ screenshotControllers.forEach { (_, screenshotController) ->
+ if (!screenshotController.isPendingSharedTransition) {
+ screenshotController.dismissScreenshot(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ }
+ }
+ }
+
+ /** Removes all screenshot related windows. */
+ fun removeWindows() {
+ screenshotControllers.forEach { (_, screenshotController) ->
+ screenshotController.removeWindow()
+ }
+ }
+
+ /**
+ * Destroys the executor. Afterwards, this class is not expected to work as intended anymore.
+ */
+ fun onDestroy() {
+ screenshotControllers.forEach { (_, screenshotController) ->
+ screenshotController.onDestroy()
+ }
+ screenshotControllers.clear()
+ displaysCollectionJob.cancel()
+ }
+
+ private fun getScreenshotController(id: Int): ScreenshotController {
+ return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) }
+ }
+
+ /** For java compatibility only. see [executeScreenshots] */
+ fun executeScreenshotsAsync(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: Consumer<Uri>,
+ requestCallback: RequestCallback
+ ) {
+ mainScope.launch {
+ executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
+ }
+ }
+
+ /**
+ * Returns a [RequestCallback] that calls [RequestCallback.onFinish] only when all callbacks for
+ * id created have finished.
+ *
+ * If any callback created calls [reportError], then following [onFinish] are not considered.
+ */
+ private class MultiResultCallbackWrapper(
+ private val originalCallback: RequestCallback,
+ ) {
+ private val idsPending = mutableSetOf<Int>()
+ private var errorReported = false
+
+ /**
+ * Creates a callback for [id].
+ *
+ * [originalCallback]'s [onFinish] will be called only when this (and the other created)
+ * callback's [onFinish] have been called.
+ */
+ fun createCallbackForId(id: Int): RequestCallback {
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TAG, "Waiting for id=$id", id)
+ idsPending += id
+ return object : RequestCallback {
+ override fun reportError() {
+ Log.d(TAG, "ReportError id=$id")
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "reportError id=$id")
+ originalCallback.reportError()
+ errorReported = true
+ }
+
+ override fun onFinish() {
+ Log.d(TAG, "onFinish id=$id")
+ if (errorReported) return
+ idsPending -= id
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "onFinish id=$id")
+ if (idsPending.isEmpty()) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
+ originalCallback.onFinish()
+ }
+ }
+ }
+ }
+ }
+
+ private companion object {
+ val TAG = LogConfig.logTag(TakeScreenshotService::class.java)
+
+ val ALLOWED_DISPLAY_TYPES =
+ listOf(
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_INTERNAL,
+ Display.TYPE_OVERLAY,
+ Display.TYPE_WIFI
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 1cdad83fb0aa..1e8542fe8f0c 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.MULTI_DISPLAY_SCREENSHOT;
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;
@@ -46,6 +47,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import android.view.Display;
import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
@@ -59,6 +61,7 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Provider;
public class TakeScreenshotService extends Service {
private static final String TAG = logTag(TakeScreenshotService.class);
@@ -82,12 +85,17 @@ public class TakeScreenshotService extends Service {
if (DEBUG_DISMISS) {
Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
}
- if (!mScreenshot.isPendingSharedTransition()) {
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ // TODO(b/295143676): move receiver inside executor when the flag is enabled.
+ mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
+ } else if (!mScreenshot.isPendingSharedTransition()) {
mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
}
}
}
};
+ private final Provider<TakeScreenshotExecutor> mTakeScreenshotExecutor;
+
/** Informs about coarse grained state of the Controller. */
public interface RequestCallback {
@@ -99,16 +107,15 @@ public class TakeScreenshotService extends Service {
}
@Inject
- public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
- DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger,
- ScreenshotNotificationsController notificationsController, Context context,
- @Background Executor bgExecutor, FeatureFlags featureFlags,
- RequestProcessor processor) {
+ public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
+ UserManager userManager, DevicePolicyManager devicePolicyManager,
+ UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController,
+ Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
+ RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
if (DEBUG_SERVICE) {
Log.d(TAG, "new " + this);
}
mHandler = new Handler(Looper.getMainLooper(), this::handleMessage);
- mScreenshot = screenshotController;
mUserManager = userManager;
mDevicePolicyManager = devicePolicyManager;
mUiEventLogger = uiEventLogger;
@@ -117,6 +124,12 @@ public class TakeScreenshotService extends Service {
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
mProcessor = processor;
+ mTakeScreenshotExecutor = takeScreenshotExecutor;
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mScreenshot = null;
+ } else {
+ mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY);
+ }
}
@Override
@@ -142,7 +155,11 @@ public class TakeScreenshotService extends Service {
if (DEBUG_SERVICE) {
Log.d(TAG, "onUnbind");
}
- mScreenshot.removeWindow();
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mTakeScreenshotExecutor.get().removeWindows();
+ } else {
+ mScreenshot.removeWindow();
+ }
unregisterReceiver(mCloseSystemDialogs);
return false;
}
@@ -150,7 +167,11 @@ public class TakeScreenshotService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
- mScreenshot.onDestroy();
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mTakeScreenshotExecutor.get().onDestroy();
+ } else {
+ mScreenshot.onDestroy();
+ }
if (DEBUG_SERVICE) {
Log.d(TAG, "onDestroy");
}
@@ -218,10 +239,17 @@ public class TakeScreenshotService extends Service {
}
Log.d(TAG, "Processing screenshot data");
- ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
+
+
+ ScreenshotData screenshotData = ScreenshotData.fromRequest(
+ request, Display.DEFAULT_DISPLAY);
try {
- mProcessor.processAsync(screenshotData,
- (data) -> dispatchToController(data, onSaved, callback));
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
+ } else {
+ mProcessor.processAsync(screenshotData, (data) ->
+ dispatchToController(data, onSaved, callback));
+ }
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to process screenshot request!", e);
logFailedRequest(request);
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 22e238c0f2ad..7d17d4c72b76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -20,9 +20,11 @@ import android.app.Service;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.RequestProcessor;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotProxyService;
+import com.android.systemui.screenshot.ScreenshotRequestProcessor;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
@@ -63,4 +65,8 @@ public abstract class ScreenshotModule {
@IntoMap
@ClassKey(AppClipsService.class)
abstract Service bindAppClipsService(AppClipsService service);
+
+ @Binds
+ abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor(
+ RequestProcessor requestProcessor);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
index 43e99393b874..f8a8a6830669 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -20,6 +20,7 @@ import android.content.ComponentName
import android.graphics.Insets
import android.graphics.Rect
import android.os.UserHandle
+import android.view.Display
import android.view.WindowManager
import com.android.internal.util.ScreenshotRequest
import com.google.common.truth.Truth.assertThat
@@ -54,6 +55,16 @@ class ScreenshotDataTest {
assertThat(data.taskId).isEqualTo(taskId)
assertThat(data.userHandle).isEqualTo(UserHandle.of(userId))
assertThat(data.topComponent).isEqualTo(component)
+ assertThat(data.displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testConstruction_notDefaultDisplayId() {
+ val request = ScreenshotRequest.Builder(type, source).build()
+
+ val data = ScreenshotData.fromRequest(request, displayId = 42)
+
+ assertThat(data.displayId).isEqualTo(42)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
new file mode 100644
index 000000000000..97c2ed45c26d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -0,0 +1,303 @@
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.net.Uri
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
+import android.view.Display.TYPE_OVERLAY
+import android.view.Display.TYPE_VIRTUAL
+import android.view.Display.TYPE_WIFI
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.ScreenshotRequest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.display
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor as ArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TakeScreenshotExecutorTest : SysuiTestCase() {
+
+ private val controller0 = mock<ScreenshotController>()
+ private val controller1 = mock<ScreenshotController>()
+ private val controllerFactory = mock<ScreenshotController.Factory>()
+ private val callback = mock<TakeScreenshotService.RequestCallback>()
+
+ private val fakeDisplayRepository = FakeDisplayRepository()
+ private val requestProcessor = FakeRequestProcessor()
+ private val topComponent = ComponentName(mContext, TakeScreenshotExecutorTest::class.java)
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val eventLogger = UiEventLoggerFake()
+
+ private val screenshotExecutor =
+ TakeScreenshotExecutor(
+ controllerFactory,
+ fakeDisplayRepository,
+ testScope,
+ requestProcessor,
+ eventLogger,
+ )
+
+ @Before
+ fun setUp() {
+ whenever(controllerFactory.create(eq(0))).thenReturn(controller0)
+ whenever(controllerFactory.create(eq(1))).thenReturn(controller1)
+ }
+
+ @Test
+ fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(controllerFactory).create(eq(0))
+ verify(controllerFactory).create(eq(1))
+
+ val capturer = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+ assertThat(capturer.value.displayId).isEqualTo(0)
+ // OnSaved callback should be different.
+ verify(controller1).handleScreenshot(capturer.capture(), any(), any())
+ assertThat(capturer.value.displayId).isEqualTo(1)
+
+ assertThat(eventLogger.numLogs()).isEqualTo(2)
+ assertThat(eventLogger.get(0).eventId)
+ .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
+ assertThat(eventLogger.get(0).packageName).isEqualTo(topComponent.packageName)
+ assertThat(eventLogger.get(1).eventId)
+ .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
+ assertThat(eventLogger.get(1).packageName).isEqualTo(topComponent.packageName)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
+ testScope.runTest {
+ setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verifyNoMoreInteractions(controllerFactory)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_allowedTypes_allCaptured() =
+ testScope.runTest {
+ whenever(controllerFactory.create(any())).thenReturn(controller0)
+
+ setDisplays(
+ display(TYPE_INTERNAL, id = 0),
+ display(TYPE_EXTERNAL, id = 1),
+ display(TYPE_OVERLAY, id = 2),
+ display(TYPE_WIFI, id = 3)
+ )
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(controller0, times(4)).handleScreenshot(any(), any(), any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
+ val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
+
+ verify(controller0).handleScreenshot(any(), any(), capturer0.capture())
+ verify(controller1).handleScreenshot(any(), any(), capturer1.capture())
+
+ verify(callback, never()).onFinish()
+
+ capturer0.value.onFinish()
+
+ verify(callback, never()).onFinish()
+
+ capturer1.value.onFinish()
+
+ verify(callback).onFinish()
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_doesNotReportFinishedIfOneFinishesOtherFails() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
+ val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
+
+ verify(controller0).handleScreenshot(any(), any(), capturer0.capture())
+ verify(controller1).handleScreenshot(any(), nullable(), capturer1.capture())
+
+ verify(callback, never()).onFinish()
+
+ capturer0.value.onFinish()
+
+ verify(callback, never()).onFinish()
+
+ capturer1.value.reportError()
+
+ verify(callback, never()).onFinish()
+ verify(callback).reportError()
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_doesNotReportFinishedAfterOneFails() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
+ val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
+
+ verify(controller0).handleScreenshot(any(), any(), capturer0.capture())
+ verify(controller1).handleScreenshot(any(), any(), capturer1.capture())
+
+ verify(callback, never()).onFinish()
+
+ capturer0.value.reportError()
+
+ verify(callback, never()).onFinish()
+ verify(callback).reportError()
+
+ capturer1.value.onFinish()
+
+ verify(callback, never()).onFinish()
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun onDestroy_propagatedToControllers() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ screenshotExecutor.onDestroy()
+ verify(controller0).onDestroy()
+ verify(controller1).onDestroy()
+ }
+
+ @Test
+ fun removeWindows_propagatedToControllers() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ screenshotExecutor.removeWindows()
+ verify(controller0).removeWindow()
+ verify(controller1).removeWindow()
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun onCloseSystemDialogsReceived_propagatedToControllers() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ screenshotExecutor.onCloseSystemDialogsReceived()
+ verify(controller0).dismissScreenshot(any())
+ verify(controller1).dismissScreenshot(any())
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun onCloseSystemDialogsReceived_someControllerHavePendingTransitions() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ whenever(controller0.isPendingSharedTransition).thenReturn(true)
+ whenever(controller1.isPendingSharedTransition).thenReturn(false)
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ screenshotExecutor.onCloseSystemDialogsReceived()
+ verify(controller0, never()).dismissScreenshot(any())
+ verify(controller1).dismissScreenshot(any())
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_controllerCalledWithRequestProcessorReturnValue() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ val screenshotRequest = createScreenshotRequest()
+ val toBeReturnedByProcessor = ScreenshotData.forTesting()
+ requestProcessor.toReturn = toBeReturnedByProcessor
+
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback)
+
+ assertThat(requestProcessor.processed)
+ .isEqualTo(ScreenshotData.fromRequest(screenshotRequest))
+
+ val capturer = ArgumentCaptor<ScreenshotData>()
+ verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+ assertThat(capturer.value).isEqualTo(toBeReturnedByProcessor)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ private suspend fun TestScope.setDisplays(vararg displays: Display) {
+ fakeDisplayRepository.emit(displays.toSet())
+ runCurrent()
+ }
+
+ private fun createScreenshotRequest() =
+ ScreenshotRequest.Builder(
+ WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+ )
+ .setTopComponent(topComponent)
+ .build()
+
+ private class FakeRequestProcessor : ScreenshotRequestProcessor {
+ var processed: ScreenshotData? = null
+ var toReturn: ScreenshotData? = null
+
+ override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+ processed = screenshot
+ return toReturn ?: screenshot
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 77f742647497..a08cda6ff158 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -27,6 +27,7 @@ import android.hardware.HardwareBuffer
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import androidx.test.filters.SmallTest
@@ -34,6 +35,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
@@ -48,6 +50,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.times
@@ -60,6 +63,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
private val application = mock<Application>()
private val controller = mock<ScreenshotController>()
+ private val controllerFactory = mock<ScreenshotController.Factory>()
+ private val takeScreenshotExecutor = mock<TakeScreenshotExecutor>()
private val userManager = mock<UserManager>()
private val requestProcessor = mock<RequestProcessor>()
private val devicePolicyManager = mock<DevicePolicyManager>()
@@ -71,21 +76,11 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
private val flags = FakeFeatureFlags()
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
- private val service =
- TakeScreenshotService(
- controller,
- userManager,
- devicePolicyManager,
- eventLogger,
- notificationsController,
- mContext,
- Runnable::run,
- flags,
- requestProcessor
- )
+ private lateinit var service: TakeScreenshotService
@Before
fun setUp() {
+ flags.set(MULTI_DISPLAY_SCREENSHOT, false)
whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
whenever(
devicePolicyManager.getScreenCaptureDisabled(
@@ -95,6 +90,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
)
.thenReturn(false)
whenever(userManager.isUserUnlocked).thenReturn(true)
+ whenever(controllerFactory.create(any())).thenReturn(controller)
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
@@ -113,14 +109,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
.whenever(requestProcessor)
.processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
- service.attach(
- mContext,
- /* thread = */ null,
- /* className = */ null,
- /* token = */ null,
- application,
- /* activityManager = */ null
- )
+ service = createService()
}
@Test
@@ -146,7 +135,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
verify(controller, times(1))
.handleScreenshot(
- eq(ScreenshotData.fromRequest(request)),
+ eq(ScreenshotData.fromRequest(request, Display.DEFAULT_DISPLAY)),
/* onSavedListener = */ any(),
/* requestCallback = */ any()
)
@@ -295,6 +284,74 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
failureEvent.packageName
)
}
+
+ @Test
+ fun takeScreenshotFullScreen_multiDisplayFlagEnabled_takeScreenshotExecutor() {
+ flags.set(MULTI_DISPLAY_SCREENSHOT, true)
+ service = createService()
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verifyZeroInteractions(controller)
+ verify(takeScreenshotExecutor, times(1)).executeScreenshotsAsync(any(), any(), any())
+
+ assertEquals("Expected one UiEvent", 0, eventLogger.numLogs())
+ }
+
+ @Test
+ fun testServiceLifecycle_multiDisplayScreenshotFlagEnabled() {
+ flags.set(MULTI_DISPLAY_SCREENSHOT, true)
+ service = createService()
+
+ service.onCreate()
+ service.onBind(null /* unused: Intent */)
+
+ service.onUnbind(null /* unused: Intent */)
+ verify(takeScreenshotExecutor, times(1)).removeWindows()
+
+ service.onDestroy()
+ verify(takeScreenshotExecutor, times(1)).onDestroy()
+ }
+
+ @Test
+ fun constructor_MultiDisplayFlagOn_screenshotControllerNotCreated() {
+ flags.set(MULTI_DISPLAY_SCREENSHOT, true)
+ clearInvocations(controllerFactory)
+
+ service = createService()
+
+ verifyZeroInteractions(controllerFactory)
+ }
+
+ private fun createService(): TakeScreenshotService {
+ val service =
+ TakeScreenshotService(
+ controllerFactory,
+ userManager,
+ devicePolicyManager,
+ eventLogger,
+ notificationsController,
+ mContext,
+ Runnable::run,
+ flags,
+ requestProcessor,
+ { takeScreenshotExecutor },
+ )
+ service.attach(
+ mContext,
+ /* thread = */ null,
+ /* className = */ null,
+ /* token = */ null,
+ application,
+ /* activityManager = */ null
+ )
+ return service
+ }
}
private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {