From 2d2b24f09db37da6dbe4b77b869969ac46c5dbaf Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Fri, 5 Aug 2022 16:36:46 +0000 Subject: [PATCH 1/2] [Partial Screensharing] Update app selector (ChooserActivity) header Updates title style (increases font size), adds an icon and hides the drag handle for media projection app selector activity which is based on ChooserActivity. Bug: 235465652 Test: atest com.android.internal.app.ChooserActivityTest Test: screenshot test in a follow-up CL Change-Id: Ib717d3f7e6132d56e8344f4e06a12a64ee63457b --- .../android/internal/app/ChooserActivity.java | 2 + core/res/res/values/symbols.xml | 5 + .../res/drawable/ic_present_to_all.xml | 25 +++++ .../layout/media_projection_app_selector.xml | 92 +++++++++++++++++++ packages/SystemUI/res/values/dimens.xml | 2 + .../MediaProjectionAppSelectorActivity.kt | 30 ++++-- .../media/dagger/MediaProjectionModule.kt | 14 ++- 7 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 packages/SystemUI/res/drawable/ic_present_to_all.xml create mode 100644 packages/SystemUI/res/layout/media_projection_app_selector.xml diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index fbabf52d096ec..ccffddaad26cd 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1619,6 +1619,8 @@ public class ChooserActivity extends ResolverActivity implements if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) { mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor(); } + mPersonalAppPredictor = null; + mWorkAppPredictor = null; } @Override // ResolverListCommunicator diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d03550c1777c1..6c7e3867316e1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -79,6 +79,11 @@ + + + + + diff --git a/packages/SystemUI/res/drawable/ic_present_to_all.xml b/packages/SystemUI/res/drawable/ic_present_to_all.xml new file mode 100644 index 0000000000000..d6c9bbe0960ef --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_present_to_all.xml @@ -0,0 +1,25 @@ + + + + diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml new file mode 100644 index 0000000000000..4ad6849e9c45d --- /dev/null +++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8ea2c0cbe8f57..f3d4c1c2f3c5c 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1446,6 +1446,8 @@ 12dp + 32dp + 60dp 40dp diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 53abd9948d030..0f1ee31e066ec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -23,35 +23,47 @@ import android.os.Binder import android.os.Bundle import android.os.IBinder import android.os.ResultReceiver -import android.view.View +import android.os.UserHandle +import android.widget.ImageView import com.android.internal.app.ChooserActivity +import com.android.internal.app.ResolverListController import com.android.internal.app.chooser.NotSelectableTargetInfo import com.android.internal.app.chooser.TargetInfo +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.util.AsyncActivityLauncher -import com.android.systemui.R; -import javax.inject.Inject +import com.android.systemui.R +import com.android.internal.R as AndroidR -class MediaProjectionAppSelectorActivity @Inject constructor( - private val activityLauncher: AsyncActivityLauncher +class MediaProjectionAppSelectorActivity constructor( + private val activityLauncher: AsyncActivityLauncher, + /** This is used to override the dependency in a screenshot test */ + @VisibleForTesting + private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? = null ) : ChooserActivity() { + override fun getLayoutResource() = + R.layout.media_projection_app_selector + public override fun onCreate(bundle: Bundle?) { val queryIntent = Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_LAUNCHER) intent.putExtra(Intent.EXTRA_INTENT, queryIntent) - // TODO(b/235465652) Use resource lexeme - intent.putExtra(Intent.EXTRA_TITLE, "Record or cast an app") + // TODO(b/240939253): update copies + val title = getString(R.string.media_projection_dialog_service_title) + intent.putExtra(Intent.EXTRA_TITLE, title) super.onCreate(bundle) - // TODO(b/235465652) we should update VisD of the title and add an icon - findViewById(R.id.title)?.visibility = View.VISIBLE + requireViewById(AndroidR.id.icon).setImageResource(R.drawable.ic_present_to_all) } override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector + override fun createListController(userHandle: UserHandle): ResolverListController = + listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle) + override fun startSelected(which: Int, always: Boolean, filtered: Boolean) { val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt index e33a1b909d482..9696998340240 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt @@ -18,8 +18,10 @@ package com.android.systemui.media.dagger import android.app.Activity import com.android.systemui.media.MediaProjectionAppSelectorActivity +import com.android.systemui.util.AsyncActivityLauncher import dagger.Binds import dagger.Module +import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @@ -29,7 +31,17 @@ abstract class MediaProjectionModule { @Binds @IntoMap @ClassKey(MediaProjectionAppSelectorActivity::class) - abstract fun provideMediaProjectionAppSelectorActivity( + abstract fun bindMediaProjectionAppSelectorActivity( activity: MediaProjectionAppSelectorActivity): Activity + companion object { + @Provides + fun provideMediaProjectionAppSelectorActivity( + activityLauncher: AsyncActivityLauncher + ): MediaProjectionAppSelectorActivity { + return MediaProjectionAppSelectorActivity( + activityLauncher + ) + } + } } From 5cb791b6f63e895a2a19856af980746a06374397 Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Fri, 5 Aug 2022 16:45:14 +0000 Subject: [PATCH 2/2] [Partial Screensharing] Add app selector activity screenshot test Adds a screenshot test for the app selector activity. It also contains changes to utility rules and classes to allow capturing the whole activity instead of a single view. Bug: 235465652 Test: atest com.android.systemui.mediaprojection.MediaProjectionAppSelectorScreenshotTest Change-Id: I558c3d0e8893091d42f90cb0358c9fd030cbc0dc --- .../android/internal/app/ChooserActivity.java | 17 +++- .../ExternalViewScreenshotTestRule.kt | 99 +++++++++++++++++++ .../screenshot/TestAppComponentFactory.kt | 60 +++++++++++ .../systemui/testing/screenshot/View.kt | 42 ++++++++ .../testing/screenshot/ViewCapture.kt | 48 +++++++++ .../screenshot/ViewScreenshotTestRule.kt | 52 +--------- 6 files changed, 266 insertions(+), 52 deletions(-) create mode 100644 packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt create mode 100644 packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt create mode 100644 packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index ccffddaad26cd..a66b405844b8e 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -63,6 +63,7 @@ import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Path; import android.graphics.drawable.AnimatedVectorDrawable; @@ -146,6 +147,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; /** @@ -281,6 +283,7 @@ public class ChooserActivity extends ResolverActivity implements private long mQueriedSharingShortcutsTimeMs; private int mCurrAvailableWidth = 0; + private Insets mLastAppliedInsets = null; private int mLastNumberOfChildren = -1; private int mMaxTargetsPerRow = 1; @@ -2533,7 +2536,11 @@ public class ChooserActivity extends ResolverActivity implements || gridAdapter.calculateChooserTargetWidth(availableWidth) || recyclerView.getAdapter() == null || availableWidth != mCurrAvailableWidth; + + boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets); + if (isLayoutUpdated + || insetsChanged || mLastNumberOfChildren != recyclerView.getChildCount()) { mCurrAvailableWidth = availableWidth; if (isLayoutUpdated) { @@ -2554,7 +2561,7 @@ public class ChooserActivity extends ResolverActivity implements return; } - if (mLastNumberOfChildren == recyclerView.getChildCount()) { + if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) { return; } @@ -2565,6 +2572,7 @@ public class ChooserActivity extends ResolverActivity implements int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter); mResolverDrawerLayout.setCollapsibleHeightReserved(offset); mEnterTransitionAnimationDelegate.markOffsetCalculated(); + mLastAppliedInsets = mSystemWindowInsets; }); } } @@ -3057,7 +3065,12 @@ public class ChooserActivity extends ResolverActivity implements mChooserMultiProfilePagerAdapter.setupContainerPadding( getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container)); } - return super.onApplyWindowInsets(v, insets); + + WindowInsets result = super.onApplyWindowInsets(v, insets); + if (mResolverDrawerLayout != null) { + mResolverDrawerLayout.requestLayout(); + } + return result; } private void setHorizontalScrollingEnabled(boolean enabled) { diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt new file mode 100644 index 0000000000000..2e391c7aacbed --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt @@ -0,0 +1,99 @@ +/* + * 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.testing.screenshot + +import android.app.Activity +import android.graphics.Color +import android.view.View +import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import platform.test.screenshot.* + +/** + * A rule that allows to run a screenshot diff test on a view that is hosted in another activity. + */ +class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { + + private val colorsRule = MaterialYouColorsRule() + private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) + private val screenshotRule = + ScreenshotTestRule( + SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + ) + private val delegateRule = + RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule) + private val matcher = UnitTestBitmapMatcher + + override fun apply(base: Statement, description: Description): Statement { + return delegateRule.apply(base, description) + } + + /** + * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in + * the context of [emulationSpec]. + */ + fun screenshotTest(goldenIdentifier: String, view: View) { + view.removeElevationRecursively() + + ScreenshotRuleAsserter.Builder(screenshotRule) + .setScreenshotProvider { view.toBitmap() } + .withMatcher(matcher) + .build() + .assertGoldenImage(goldenIdentifier) + } + + /** + * Compare the content of the [activity] with the golden image identified by [goldenIdentifier] + * in the context of [emulationSpec]. + */ + fun activityScreenshotTest( + goldenIdentifier: String, + activity: Activity, + ) { + val rootView = activity.window.decorView + + // Hide system bars, remove insets, focus and make sure device-specific cutouts + // don't affect screenshots + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val window = activity.window + window.setDecorFitsSystemWindows(false) + WindowInsetsControllerCompat(window, rootView).apply { + hide(WindowInsetsCompat.Type.systemBars()) + systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + window.attributes = + window.attributes.apply { + layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } + + rootView.removeInsetsRecursively() + activity.currentFocus?.clearFocus() + } + + screenshotTest(goldenIdentifier, rootView) + } +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt new file mode 100644 index 0000000000000..98e9aaf44ceb2 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt @@ -0,0 +1,60 @@ +/* + * 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.testing.screenshot + +import android.app.Activity +import android.content.Intent +import androidx.core.app.AppComponentFactory + +class TestAppComponentFactory : AppComponentFactory() { + + init { + instance = this + } + + private val overrides: MutableMap Activity> = hashMapOf() + + fun clearOverrides() { + overrides.clear() + } + + fun registerActivityOverride(activity: Class, provider: () -> T) { + overrides[activity.name] = provider + } + + override fun instantiateActivityCompat( + cl: ClassLoader, + className: String, + intent: Intent? + ): Activity { + return overrides + .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) } + .invoke() + } + + companion object { + + private var instance: TestAppComponentFactory? = null + + fun getInstance(): TestAppComponentFactory = + instance + ?: error( + "TestAppComponentFactory is not initialized, " + + "did you specify it in the manifest?" + ) + } +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt new file mode 100644 index 0000000000000..b84d26a7034a7 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt @@ -0,0 +1,42 @@ +/* + * 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.testing.screenshot + +import android.view.View +import android.view.ViewGroup +import com.android.systemui.util.children +import android.view.WindowInsets + +/** + * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to + * disable it for any view in the hierarchy. + */ +fun View.removeElevationRecursively() { + this.elevation = 0f + (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively) +} + +/** + * Different devices could have different insets (e.g. different height of the navigation bar or + * taskbar). This method dispatches empty insets to the whole view hierarchy and removes + * the original listener, so the views won't receive real insets. + */ +fun View.removeInsetsRecursively() { + this.dispatchApplyWindowInsets(WindowInsets.CONSUMED) + this.setOnApplyWindowInsetsListener(null) + (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively) +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt index c609e6f8b4bf9..cdedc64ed0e9c 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt @@ -1,10 +1,12 @@ package com.android.systemui.testing.screenshot +import android.annotation.WorkerThread import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.graphics.Bitmap import android.graphics.Canvas +import android.graphics.HardwareRenderer import android.graphics.Rect import android.os.Build import android.os.Handler @@ -19,8 +21,13 @@ import androidx.annotation.RequiresApi import androidx.concurrent.futures.ResolvableFuture import androidx.test.annotation.ExperimentalTestApi import androidx.test.core.internal.os.HandlerExecutor +import androidx.test.espresso.Espresso import androidx.test.platform.graphics.HardwareRendererCompat +import com.google.common.util.concurrent.FutureCallback +import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.runBlocking /* * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to @@ -61,6 +68,47 @@ fun View.captureToBitmap(window: Window? = null): ListenableFuture { return bitmapFuture } +/** + * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of + * [captureToBitmap]. + */ +@WorkerThread +@ExperimentalTestApi +@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) +fun View.toBitmap(window: Window? = null): Bitmap { + if (Looper.getMainLooper() == Looper.myLooper()) { + error("toBitmap() can't be called from the main thread") + } + + if (!HardwareRenderer.isDrawingEnabled()) { + error("Hardware rendering is not enabled") + } + + // Make sure we are idle. + Espresso.onIdle() + + val mainExecutor = context.mainExecutor + return runBlocking { + suspendCoroutine { continuation -> + Futures.addCallback( + captureToBitmap(window), + object : FutureCallback { + override fun onSuccess(result: Bitmap) { + continuation.resumeWith(Result.success(result)) + } + + override fun onFailure(t: Throwable) { + continuation.resumeWith(Result.failure(t)) + } + }, + // We know that we are not on the main thread, so we can block the current + // thread and wait for the result in the main thread. + mainExecutor, + ) + } + } +} + /** * Trigger a redraw of the given view. * diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index 60130e1086ef0..b3f10718525a2 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -19,21 +19,13 @@ package com.android.systemui.testing.screenshot import android.app.Activity import android.app.Dialog import android.graphics.Bitmap -import android.graphics.HardwareRenderer -import android.os.Looper import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.view.Window import androidx.activity.ComponentActivity -import androidx.test.espresso.Espresso import androidx.test.ext.junit.rules.ActivityScenarioRule -import com.google.common.util.concurrent.FutureCallback -import com.google.common.util.concurrent.Futures -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.rules.RuleChain import org.junit.rules.TestRule @@ -86,6 +78,8 @@ class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { // Elevation/shadows is not deterministic when doing hardware rendering, so we disable // it for any view in the hierarchy. window.decorView.removeElevationRecursively() + + activity.currentFocus?.clearFocus() } // We call onActivity again because it will make sure that our Activity is done measuring, @@ -147,53 +141,11 @@ class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { } } - private fun View.removeElevationRecursively() { - this.elevation = 0f - - if (this is ViewGroup) { - repeat(childCount) { i -> getChildAt(i).removeElevationRecursively() } - } - } - private fun Dialog.toBitmap(): Bitmap { val window = window return window.decorView.toBitmap(window) } - private fun View.toBitmap(window: Window? = null): Bitmap { - if (Looper.getMainLooper() == Looper.myLooper()) { - error("toBitmap() can't be called from the main thread") - } - - if (!HardwareRenderer.isDrawingEnabled()) { - error("Hardware rendering is not enabled") - } - - // Make sure we are idle. - Espresso.onIdle() - - val mainExecutor = context.mainExecutor - return runBlocking { - suspendCoroutine { continuation -> - Futures.addCallback( - captureToBitmap(window), - object : FutureCallback { - override fun onSuccess(result: Bitmap?) { - continuation.resumeWith(Result.success(result!!)) - } - - override fun onFailure(t: Throwable) { - continuation.resumeWith(Result.failure(t)) - } - }, - // We know that we are not on the main thread, so we can block the current - // thread and wait for the result in the main thread. - mainExecutor, - ) - } - } - } - enum class Mode(val layoutParams: LayoutParams) { WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)), MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),