PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/robotium-solo/src/main/java/com/jayway/android/robotium/solo/ScreenshotTaker.java

https://github.com/saneeshjose/robotium
Java | 413 lines | 240 code | 49 blank | 124 comment | 39 complexity | fc6d82301b93e29424bc7f205bfb850b MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.jayway.android.robotium.solo;
  2. import java.io.File;
  3. import java.io.FileOutputStream;
  4. import java.text.SimpleDateFormat;
  5. import java.util.ArrayList;
  6. import java.util.Date;
  7. import java.util.concurrent.CountDownLatch;
  8. import android.graphics.Bitmap;
  9. import android.graphics.Canvas;
  10. import android.graphics.Picture;
  11. import android.opengl.GLSurfaceView;
  12. import android.opengl.GLSurfaceView.Renderer;
  13. import android.os.Environment;
  14. import android.os.Handler;
  15. import android.os.HandlerThread;
  16. import android.os.Message;
  17. import android.os.SystemClock;
  18. import android.util.Log;
  19. import android.view.View;
  20. import android.webkit.WebView;
  21. /**
  22. * Contains screenshot methods like: takeScreenshot(final View, final String name), startScreenshotSequence(final String name, final int quality, final int frameDelay, final int maxFrames),
  23. * stopScreenshotSequence().
  24. *
  25. *
  26. * @author Renas Reda, renasreda@gmail.com
  27. *
  28. */
  29. class ScreenshotTaker {
  30. private final ActivityUtils activityUtils;
  31. private final String LOG_TAG = "Robotium";
  32. private ScreenshotSequenceThread screenshotSequenceThread = null;
  33. private HandlerThread screenShotSaverThread = null;
  34. private ScreenShotSaver screenShotSaver = null;
  35. private final ViewFetcher viewFetcher;
  36. private final Sleeper sleeper;
  37. /**
  38. * Constructs this object.
  39. *
  40. * @param activityUtils the {@code ActivityUtils} instance
  41. * @param viewFetcher the {@code ViewFetcher} instance
  42. * @param sleeper the {@code Sleeper} instance
  43. *
  44. */
  45. ScreenshotTaker(ActivityUtils activityUtils, ViewFetcher viewFetcher, Sleeper sleeper) {
  46. this.activityUtils = activityUtils;
  47. this.viewFetcher = viewFetcher;
  48. this.sleeper = sleeper;
  49. }
  50. /**
  51. * Takes a screenshot and saves it in "/sdcard/Robotium-Screenshots/".
  52. * Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.
  53. *
  54. * @param view the view to take screenshot of
  55. * @param name the name to give the screenshot image
  56. * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality).
  57. */
  58. public void takeScreenshot(final String name, final int quality) {
  59. View decorView = getScreenshotView();
  60. if(decorView == null)
  61. return;
  62. initScreenShotSaver();
  63. ScreenshotRunnable runnable = new ScreenshotRunnable(decorView, name, quality);
  64. activityUtils.getCurrentActivity(false).runOnUiThread(runnable);
  65. }
  66. /**
  67. * Takes a screenshot sequence and saves the images with the specified name prefix in "/sdcard/Robotium-Screenshots/".
  68. *
  69. * The name prefix is appended with "_" + sequence_number for each image in the sequence,
  70. * where numbering starts at 0.
  71. *
  72. * Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in the
  73. * AndroidManifest.xml of the application under test.
  74. *
  75. * Taking a screenshot will take on the order of 40-100 milliseconds of time on the
  76. * main UI thread. Therefore it is possible to mess up the timing of tests if
  77. * the frameDelay value is set too small.
  78. *
  79. * At present multiple simultaneous screenshot sequences are not supported.
  80. * This method will throw an exception if stopScreenshotSequence() has not been
  81. * called to finish any prior sequences.
  82. *
  83. * @param name the name prefix to give the screenshot
  84. * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality)
  85. * @param frameDelay the time in milliseconds to wait between each frame
  86. * @param maxFrames the maximum number of frames that will comprise this sequence
  87. *
  88. */
  89. public void startScreenshotSequence(final String name, final int quality, final int frameDelay, final int maxFrames) {
  90. initScreenShotSaver();
  91. if(screenshotSequenceThread != null) {
  92. throw new RuntimeException("only one screenshot sequence is supported at a time");
  93. }
  94. screenshotSequenceThread = new ScreenshotSequenceThread(name, quality, frameDelay, maxFrames);
  95. screenshotSequenceThread.start();
  96. }
  97. /**
  98. * Causes a screenshot sequence to end.
  99. *
  100. * If this method is not called to end a sequence and a prior sequence is still in
  101. * progress, startScreenshotSequence() will throw an exception.
  102. */
  103. public void stopScreenshotSequence() {
  104. if(screenshotSequenceThread != null) {
  105. screenshotSequenceThread.interrupt();
  106. screenshotSequenceThread = null;
  107. }
  108. }
  109. /**
  110. * Gets the proper view to use for a screenshot.
  111. */
  112. private View getScreenshotView() {
  113. View decorView = viewFetcher.getRecentDecorView(viewFetcher.getWindowDecorViews());
  114. final long endTime = SystemClock.uptimeMillis() + Timeout.getSmallTimeout();
  115. while (decorView == null) {
  116. final boolean timedOut = SystemClock.uptimeMillis() > endTime;
  117. if (timedOut){
  118. return null;
  119. }
  120. sleeper.sleepMini();
  121. decorView = viewFetcher.getRecentDecorView(viewFetcher.getWindowDecorViews());
  122. }
  123. wrapAllGLViews(decorView);
  124. return decorView;
  125. }
  126. /**
  127. * Extract and wrap the all OpenGL ES Renderer.
  128. */
  129. private void wrapAllGLViews(View decorView) {
  130. ArrayList<GLSurfaceView> currentViews = viewFetcher.getCurrentViews(GLSurfaceView.class, decorView);
  131. final CountDownLatch latch = new CountDownLatch(currentViews.size());
  132. for (GLSurfaceView glView : currentViews) {
  133. Object renderContainer = new Reflect(glView).field("mGLThread")
  134. .type(GLSurfaceView.class).out(Object.class);
  135. Renderer renderer = new Reflect(renderContainer).field("mRenderer").out(Renderer.class);
  136. if (renderer == null) {
  137. renderer = new Reflect(glView).field("mRenderer").out(Renderer.class);
  138. renderContainer = glView;
  139. }
  140. if (renderer == null) {
  141. latch.countDown();
  142. continue;
  143. }
  144. if (renderer instanceof GLRenderWrapper) {
  145. GLRenderWrapper wrapper = (GLRenderWrapper) renderer;
  146. wrapper.setTakeScreenshot();
  147. wrapper.setLatch(latch);
  148. } else {
  149. GLRenderWrapper wrapper = new GLRenderWrapper(glView, renderer, latch);
  150. new Reflect(renderContainer).field("mRenderer").in(wrapper);
  151. }
  152. }
  153. try {
  154. latch.await();
  155. } catch (InterruptedException ex) {
  156. ex.printStackTrace();
  157. }
  158. }
  159. /**
  160. * Returns a bitmap of a given WebView.
  161. *
  162. * @param webView the webView to save a bitmap from
  163. * @return a bitmap of the given web view
  164. *
  165. */
  166. private Bitmap getBitmapOfWebView(final WebView webView){
  167. Picture picture = webView.capturePicture();
  168. Bitmap b = Bitmap.createBitmap( picture.getWidth(), picture.getHeight(), Bitmap.Config.ARGB_8888);
  169. Canvas c = new Canvas(b);
  170. picture.draw(c);
  171. return b;
  172. }
  173. /**
  174. * Returns a bitmap of a given View.
  175. *
  176. * @param view the view to save a bitmap from
  177. * @return a bitmap of the given view
  178. *
  179. */
  180. private Bitmap getBitmapOfView(final View view){
  181. view.destroyDrawingCache();
  182. view.buildDrawingCache(false);
  183. Bitmap orig = view.getDrawingCache();
  184. Bitmap.Config config = orig.getConfig();
  185. if(config == null)
  186. config = Bitmap.Config.ARGB_8888;
  187. Bitmap b = orig.copy(config, false);
  188. view.destroyDrawingCache();
  189. return b;
  190. }
  191. /**
  192. * Returns a proper filename depending on if name is given or not.
  193. *
  194. * @param name the given name
  195. * @return a proper filename depedning on if a name is given or not
  196. *
  197. */
  198. private String getFileName(final String name){
  199. SimpleDateFormat sdf = new SimpleDateFormat("ddMMyy-hhmmss");
  200. String fileName = null;
  201. if(name == null){
  202. fileName = sdf.format( new Date()).toString()+ ".jpg";
  203. }
  204. else {
  205. fileName = name + ".jpg";
  206. }
  207. return fileName;
  208. }
  209. /**
  210. * This method initializes the aysnc screenshot saving logic
  211. */
  212. private void initScreenShotSaver() {
  213. if(screenShotSaverThread == null || screenShotSaver == null) {
  214. screenShotSaverThread = new HandlerThread("ScreenShotSaver");
  215. screenShotSaverThread.start();
  216. screenShotSaver = new ScreenShotSaver(screenShotSaverThread);
  217. }
  218. }
  219. /**
  220. * This is the thread which causes a screenshot sequence to happen
  221. * in parallel with testing.
  222. */
  223. private class ScreenshotSequenceThread extends Thread {
  224. private int seqno = 0;
  225. private String name;
  226. private int quality;
  227. private int frameDelay;
  228. private int maxFrames;
  229. private boolean keepRunning = true;
  230. public ScreenshotSequenceThread(String _name, int _quality, int _frameDelay, int _maxFrames) {
  231. name = _name;
  232. quality = _quality;
  233. frameDelay = _frameDelay;
  234. maxFrames = _maxFrames;
  235. }
  236. public void run() {
  237. while(seqno < maxFrames) {
  238. if(!keepRunning || Thread.interrupted()) break;
  239. doScreenshot();
  240. seqno++;
  241. try {
  242. Thread.sleep(frameDelay);
  243. } catch (InterruptedException e) {
  244. }
  245. }
  246. screenshotSequenceThread = null;
  247. }
  248. public void doScreenshot() {
  249. View v = getScreenshotView();
  250. if(v == null) keepRunning = false;
  251. String final_name = name+"_"+seqno;
  252. ScreenshotRunnable r = new ScreenshotRunnable(v, final_name, quality);
  253. Log.d(LOG_TAG, "taking screenshot "+final_name);
  254. activityUtils.getCurrentActivity(false).runOnUiThread(r);
  255. }
  256. public void interrupt() {
  257. keepRunning = false;
  258. super.interrupt();
  259. }
  260. }
  261. /**
  262. * Here we have a Runnable which is responsible for taking the actual screenshot,
  263. * and then posting the bitmap to a Handler which will save it.
  264. *
  265. * This Runnable is run on the UI thread.
  266. */
  267. private class ScreenshotRunnable implements Runnable {
  268. private View view;
  269. private String name;
  270. private int quality;
  271. public ScreenshotRunnable(final View _view, final String _name, final int _quality) {
  272. view = _view;
  273. name = _name;
  274. quality = _quality;
  275. }
  276. public void run() {
  277. if(view !=null){
  278. Bitmap b;
  279. if(view instanceof WebView){
  280. b = getBitmapOfWebView((WebView) view);
  281. }
  282. else{
  283. b = getBitmapOfView(view);
  284. }
  285. if(b != null)
  286. screenShotSaver.saveBitmap(b, name, quality);
  287. else
  288. Log.d(LOG_TAG, "NULL BITMAP!!");
  289. }
  290. }
  291. }
  292. /**
  293. * This class is a Handler which deals with saving the screenshots on a separate thread.
  294. *
  295. * The screenshot logic by necessity has to run on the ui thread. However, in practice
  296. * it seems that saving a screenshot (with quality 100) takes approx twice as long
  297. * as taking it in the first place.
  298. *
  299. * Saving the screenshots in a separate thread like this will thus make the screenshot
  300. * process approx 3x faster as far as the main thread is concerned.
  301. *
  302. */
  303. private class ScreenShotSaver extends Handler {
  304. public ScreenShotSaver(HandlerThread thread) {
  305. super(thread.getLooper());
  306. }
  307. /**
  308. * This method posts a Bitmap with meta-data to the Handler queue.
  309. *
  310. * @param bitmap the bitmap to save
  311. * @param name the name of the file
  312. * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality).
  313. */
  314. public void saveBitmap(Bitmap bitmap, String name, int quality) {
  315. Message message = this.obtainMessage();
  316. message.arg1 = quality;
  317. message.obj = bitmap;
  318. message.getData().putString("name", name);
  319. this.sendMessage(message);
  320. }
  321. /**
  322. * Here we process the Handler queue and save the bitmaps.
  323. *
  324. * @param message A Message containing the bitmap to save, and some metadata.
  325. */
  326. public void handleMessage(Message message) {
  327. String name = message.getData().getString("name");
  328. int quality = message.arg1;
  329. Bitmap b = (Bitmap)message.obj;
  330. if(b != null) {
  331. saveFile(name, b, quality);
  332. b.recycle();
  333. }
  334. else {
  335. Log.d(LOG_TAG, "NULL BITMAP!!");
  336. }
  337. }
  338. /**
  339. * Saves a file.
  340. *
  341. * @param name the name of the file
  342. * @param b the bitmap to save
  343. * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality).
  344. *
  345. */
  346. private void saveFile(String name, Bitmap b, int quality){
  347. FileOutputStream fos = null;
  348. String fileName = getFileName(name);
  349. File directory = new File(Environment.getExternalStorageDirectory() + "/Robotium-Screenshots/");
  350. directory.mkdir();
  351. File fileToSave = new File(directory,fileName);
  352. try {
  353. fos = new FileOutputStream(fileToSave);
  354. if (b.compress(Bitmap.CompressFormat.JPEG, quality, fos) == false)
  355. Log.d(LOG_TAG, "Compress/Write failed");
  356. fos.flush();
  357. fos.close();
  358. } catch (Exception e) {
  359. Log.d(LOG_TAG, "Can't save the screenshot! Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.");
  360. e.printStackTrace();
  361. }
  362. }
  363. }
  364. }