/
DecorViewScreenshotStrategy.java
127 lines (108 loc) · 4.42 KB
/
DecorViewScreenshotStrategy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package tools.fastlane.screengrab;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Looper;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.matcher.ViewMatchers;
import android.view.View;
import org.hamcrest.Matcher;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* <p>Screenshot strategy that captures the contents of a Window's decor view.</p>
*
* <p>Advantages compared to {@link UiAutomatorScreenshotStrategy}:</p>
*
* <ul>
* <li>Works down to API level 8</li>
* <li>Uses Espresso for action synchronization internally, so requires less matching
* setup in your tests</li>
* </ul>
*
* Known limitations:
* <ul>
* <li>Does not work on Android N</li>
* <li>Does not correctly capture depth/shadows in Material UI</li>
* <li>Does not correctly capture multi-window situations (dialogs, etc.)</li>
* <li>Does not correctly capture specialized surface views (Google Maps, video players, etc.)</li>
* </ul>
*/
public class DecorViewScreenshotStrategy implements ScreenshotStrategy {
@Override
public void takeScreenshot(String screenshotName, ScreenshotCallback callback) {
Espresso.onView(ViewMatchers.isRoot()).perform(new ScreenshotViewAction(screenshotName, callback));
}
static class ScreenshotViewAction implements ViewAction {
private final String screenshotName;
private final ScreenshotCallback callback;
public ScreenshotViewAction(String screenshotName, ScreenshotCallback callback) {
this.screenshotName = screenshotName;
this.callback = callback;
}
@Override
public Matcher<View> getConstraints() {
return ViewMatchers.isDisplayed();
}
@Override
public String getDescription() {
return "taking screenshot of the Activity";
}
@Override
public void perform(UiController uiController, View view) {
final Activity activity = scanForActivity(view.getContext());
if (activity == null) {
throw new IllegalStateException("Couldn't get the activity from the view context");
}
try {
callback.screenshotCaptured(screenshotName, takeScreenshot(activity));
} catch (Exception e) {
throw new RuntimeException("Unable to capture screenshot.", e);
}
}
private Activity scanForActivity(Context context) {
if (context == null) {
return null;
} else if (context instanceof Activity) {
return (Activity) context;
} else if (context instanceof ContextWrapper) {
return scanForActivity(((ContextWrapper) context).getBaseContext());
}
return null;
}
private static Bitmap takeScreenshot(final Activity activity) throws IOException {
View view = activity.getWindow().getDecorView();
final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
if (Looper.myLooper() == Looper.getMainLooper()) {
// On main thread already, Just Do It™.
drawDecorViewToBitmap(activity, bitmap);
} else {
// On a background thread, post to main.
final CountDownLatch latch = new CountDownLatch(1);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
drawDecorViewToBitmap(activity, bitmap);
} finally {
latch.countDown();
}
}
});
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException("Unable to capture screenshot", e);
}
}
return bitmap;
}
private static void drawDecorViewToBitmap(Activity activity, Bitmap bitmap) {
activity.getWindow().getDecorView().draw(new Canvas(bitmap));
}
}
}