Skip to content

Commit 4aeb7a7

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Add ViewActions.captureToBitmap
This API will replace ViewInteraction.captureToBitmap in a subsequent change. PiperOrigin-RevId: 615565807
1 parent de63a74 commit 4aeb7a7

File tree

7 files changed

+146
-2
lines changed

7 files changed

+146
-2
lines changed

espresso/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ The following artifacts were released:
2727

2828
**API Changes**
2929

30+
* Delete ViewInteraction.captureToBitmap in favor of ViewActions.captureToBitmap
31+
3032
**Breaking API Changes**
3133

3234
**Known Issues**

espresso/core/java/androidx/test/espresso/action/BUILD

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# View Actions for espresso.
33

44
load("@build_bazel_rules_android//android:rules.bzl", "android_library")
5+
load("//build_extensions:kt_android_library.bzl", "kt_android_library")
56

67
licenses(["notice"])
78

@@ -30,15 +31,20 @@ android_library(
3031
],
3132
)
3233

33-
android_library(
34+
kt_android_library(
3435
name = "action",
3536
srcs = glob(
36-
["*.java"],
37+
[
38+
"*.java",
39+
"*.kt",
40+
],
3741
exclude = ADAPTER_VIEW_PROTOCOL,
3842
),
3943
javacopts = COMMON_JAVACOPTS,
4044
deps = [
4145
":adapter_view_protocol",
46+
"//annotation/java/androidx/test/annotation",
47+
"//core/java/androidx/test/core:core_not_test_only",
4248
"//espresso/core/java/androidx/test/espresso:framework",
4349
"//espresso/core/java/androidx/test/espresso:interface",
4450
"//espresso/core/java/androidx/test/espresso/matcher",
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package androidx.test.espresso.action
2+
3+
import android.os.Handler
4+
import android.os.Looper
5+
import android.view.View
6+
import androidx.test.annotation.ExperimentalTestApi
7+
import androidx.test.core.internal.os.HandlerExecutor
8+
import androidx.test.core.view.captureToBitmap
9+
import androidx.test.espresso.IdlingRegistry
10+
import androidx.test.espresso.UiController
11+
import androidx.test.espresso.ViewAction
12+
import java.util.Locale
13+
import java.util.concurrent.TimeUnit
14+
import org.hamcrest.Matcher
15+
import org.hamcrest.Matchers.any
16+
17+
@ExperimentalTestApi
18+
class CaptureToBitmapAction(val bitmapReceiver: ViewActions.BitmapReceiver) : ViewAction {
19+
override fun getConstraints(): Matcher<View> {
20+
return any(View::class.java)
21+
}
22+
23+
override fun getDescription(): String {
24+
return String.format(Locale.ROOT, "capture view to Bitmap")
25+
}
26+
27+
override fun perform(uiController: UiController, view: View) {
28+
uiController.loopMainThreadUntilIdle()
29+
// Create an idling resource for the bitmap creation work.
30+
val captureIdlingResource = RunnableIdlingResource()
31+
IdlingRegistry.getInstance().register(captureIdlingResource)
32+
33+
// Have the bitmap mark the idling resource as idle when it completes.
34+
val futureBitmap = view.captureToBitmap()
35+
val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
36+
futureBitmap.addListener(captureIdlingResource, mainExecutor)
37+
38+
// Wait for it to complete and unregister
39+
uiController.loopMainThreadUntilIdle()
40+
IdlingRegistry.getInstance().unregister(captureIdlingResource)
41+
42+
// Acquire the bitmap (should be instant)
43+
val destBitmap = futureBitmap.get(1, TimeUnit.MILLISECONDS)
44+
bitmapReceiver.onBitmapCaptured(destBitmap)
45+
}
46+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package androidx.test.espresso.action
2+
3+
import androidx.test.espresso.IdlingResource
4+
import java.util.concurrent.atomic.AtomicBoolean
5+
6+
/** An idling resource that waits for a callback to mark it as idle. */
7+
internal class RunnableIdlingResource : IdlingResource, Runnable {
8+
9+
private var idle: AtomicBoolean = AtomicBoolean(false)
10+
private var resourceCallback: IdlingResource.ResourceCallback? = null
11+
12+
override fun getName(): String {
13+
return "RunnableIdlingResource"
14+
}
15+
16+
override fun isIdleNow(): Boolean {
17+
return idle.get()
18+
}
19+
20+
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
21+
this.resourceCallback = callback
22+
}
23+
24+
override fun run() {
25+
idle.set(true)
26+
resourceCallback?.onTransitionToIdle()
27+
}
28+
}

espresso/core/java/androidx/test/espresso/action/ViewActions.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.hamcrest.Matchers.any;
2222
import static org.hamcrest.Matchers.is;
2323

24+
import android.graphics.Bitmap;
2425
import android.net.Uri;
2526
import android.util.Log;
2627
import android.util.Pair;
@@ -29,6 +30,7 @@
2930
import android.view.MotionEvent;
3031
import android.view.View;
3132
import androidx.annotation.NonNull;
33+
import androidx.test.annotation.ExperimentalTestApi;
3234
import androidx.test.espresso.PerformException;
3335
import androidx.test.espresso.UiController;
3436
import androidx.test.espresso.ViewAction;
@@ -568,4 +570,23 @@ public static ViewAction repeatedlyUntil(
568570
return actionWithAssertions(
569571
new RepeatActionUntilViewState(action, desiredStateMatcher, maxAttempts));
570572
}
573+
574+
@ExperimentalTestApi
575+
public interface BitmapReceiver {
576+
void onBitmapCaptured(Bitmap bitmap);
577+
}
578+
579+
/**
580+
* Returns an action that captures an image of the desired View and performs some other operation
581+
* on the result.
582+
*
583+
* @see androidx.test.core.view.ViewCapture.captureToBitmap
584+
* @param bitmapReceiver the BitmapReceiver that receives the result. This will be called on main
585+
* thread.
586+
* @return the ViewAction
587+
*/
588+
@ExperimentalTestApi
589+
public static ViewAction captureToBitmap(BitmapReceiver bitmapReceiver) {
590+
return new CaptureToBitmapAction(bitmapReceiver);
591+
}
571592
}

espresso/core/javatests/androidx/test/espresso/action/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,18 @@ axt_android_library_test(
391391
"@maven//:junit_junit",
392392
],
393393
)
394+
395+
axt_android_library_test(
396+
name = "CaptureToBitmapActionTest",
397+
srcs =
398+
["CaptureToBitmapActionTest.kt"],
399+
deps = [
400+
"//core",
401+
"//espresso/core/java/androidx/test/espresso",
402+
"//espresso/core/java/androidx/test/espresso:espresso_compiletime",
403+
"//ext/junit",
404+
"//testapps/ui_testapp/java/androidx/test/ui/app:lib_exported",
405+
"@maven//:com_google_truth_truth",
406+
"@maven//:junit_junit",
407+
],
408+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package androidx.test.espresso.action
2+
3+
import androidx.test.core.graphics.writeToTestStorage
4+
import androidx.test.espresso.Espresso.onView
5+
import androidx.test.espresso.action.ViewActions.captureToBitmap
6+
import androidx.test.espresso.matcher.ViewMatchers
7+
import androidx.test.ext.junit.rules.ActivityScenarioRule
8+
import androidx.test.ext.junit.runners.AndroidJUnit4
9+
import androidx.test.ui.app.MainActivity
10+
import java.io.IOException
11+
import org.junit.Rule
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
15+
/** A simple scuba test to ensure captureToImage works from hjava */
16+
@RunWith(AndroidJUnit4::class)
17+
class CaptureToBitmapActionTest {
18+
@JvmField @Rule var activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
19+
20+
@Test
21+
@Throws(IOException::class)
22+
fun captureToBitmapAndSave() {
23+
onView(ViewMatchers.isRoot())
24+
.perform(captureToBitmap { bitmap -> bitmap.writeToTestStorage("captureToBitmapAndSave") })
25+
}
26+
}

0 commit comments

Comments
 (0)