Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add UiAutomator screenshot strategy (#5621)
Refactors `Screengrab` into more collaborators, while keeping the existing `Screengrab` interface working. The goal is to introduce a new strategy for screenshot capture based on [UiAutomation](#2080 (comment)). It allows capture on Android N, and more correctly captures multi-layer/window situations (dialogs, etc.) Some verification is needed to determine if it also helps Google Maps & video player use-cases. The new strategy depends on `UiAutomation` which has an API >= 18 requirement. We enforce that in code, and override the manifest merger checks that would force the (optional) API level requirement onto consuming apps. So, our `minSdkVersion` remains **8** for the library. The `JUnit3StyleTests` are removed, as they are not a recommended configuration. The example application and tests have been slightly expanded to include a button that opens a dialog to verify multi-window capture by `UiAutomatorScreenshotStrategy`
- Loading branch information
Showing
20 changed files
with
444 additions
and
231 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 0 additions & 47 deletions
47
screengrab/example/src/androidTest/java/tools/fastlane/localetester/JUnit3StyleTests.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
screengrab/example/src/main/java/tools/fastlane/localetester/AnotherActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,39 @@ | ||
package tools.fastlane.localetester; | ||
|
||
import android.content.DialogInterface; | ||
import android.os.Bundle; | ||
import android.support.v7.app.ActionBarActivity; | ||
import android.support.v7.app.AlertDialog; | ||
import android.view.View; | ||
import android.widget.Button; | ||
|
||
public class AnotherActivity extends ActionBarActivity { | ||
|
||
private Button showDialogButton; | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(tools.fastlane.localetester.R.layout.activity_another); | ||
|
||
showDialogButton = (Button)findViewById(R.id.show_dialog_button); | ||
showDialogButton.setOnClickListener(new View.OnClickListener() { | ||
@Override | ||
public void onClick(View v) { | ||
showDialog(); | ||
} | ||
}); | ||
} | ||
|
||
private void showDialog() { | ||
new AlertDialog.Builder(this) | ||
.setMessage(R.string.hello) | ||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialog, int which) { | ||
dialog.dismiss(); | ||
} | ||
}) | ||
.show(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
#Wed Oct 21 11:34:03 PDT 2015 | ||
#Wed Aug 03 15:15:30 EDT 2016 | ||
distributionBase=GRADLE_USER_HOME | ||
distributionPath=wrapper/dists | ||
zipStoreBase=GRADLE_USER_HOME | ||
zipStorePath=wrapper/dists | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
<manifest xmlns:tools="http://schemas.android.com/tools" | ||
package="tools.fastlane.screengrab"> | ||
|
||
<!-- We will manually check for API level >= 18 before using UiAutomator --> | ||
<uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18" /> | ||
</manifest> |
127 changes: 127 additions & 0 deletions
127
...b/screengrab-lib/src/main/java/tools.fastlane.screengrab/DecorViewScreenshotStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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)); | ||
} | ||
} | ||
} |
Oops, something went wrong.