/* * Copyright (C) 2012 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.uiautomator.actions; import com.android.uiautomator.UiAutomatorModel; import com.android.uiautomator.UiAutomatorViewer; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; public class ScreenshotAction extends Action { UiAutomatorViewer mViewer; public ScreenshotAction(UiAutomatorViewer viewer) { super("&Device Screenshot"); mViewer = viewer; } @Override public ImageDescriptor getImageDescriptor() { return ImageHelper.loadImageDescriptorFromResource("images/screenshot.png"); } @Override public void run() { ProgressMonitorDialog dialog = new ProgressMonitorDialog(mViewer.getShell()); try { dialog.run(true, false, new IRunnableWithProgress() { private void showError(final String msg, final Throwable t, IProgressMonitor monitor) { monitor.done(); mViewer.getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { Status s = new Status(IStatus.ERROR, "Screenshot", msg, t); ErrorDialog.openError( mViewer.getShell(), "Error", "Cannot take screenshot", s); } }); } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { ProcRunner procRunner = null; String serial = System.getenv("ANDROID_SERIAL"); File tmpDir = null; File xmlDumpFile = null; File screenshotFile = null; int retCode = -1; try { tmpDir = File.createTempFile("uiautomatorviewer_", ""); tmpDir.delete(); if (!tmpDir.mkdirs()) throw new IOException("Failed to mkdir"); xmlDumpFile = File.createTempFile("dump_", ".xml", tmpDir); screenshotFile = File.createTempFile("screenshot_", ".png", tmpDir); } catch (IOException e) { e.printStackTrace(); showError("Cannot get temp directory", e, monitor); return; } // boiler plates to do a bunch of adb stuff to take XML snapshot and screenshot monitor.beginTask("Getting UI status dump from device...", IProgressMonitor.UNKNOWN); monitor.subTask("Detecting device..."); procRunner = getAdbRunner(serial, "shell", "ls", "/system/bin/uiautomator"); try { retCode = procRunner.run(30000); } catch (IOException e) { e.printStackTrace(); showError("Failed to detect device", e, monitor); return; } if (retCode != 0) { showError("No device or multiple devices connected. " + "Use ANDROID_SERIAL environment variable " + "if you have multiple devices", null, monitor); return; } if (procRunner.getOutputBlob().indexOf("No such file or directory") != -1) { showError("/system/bin/uiautomator not found on device", null, monitor); return; } monitor.subTask("Deleting old UI XML snapshot ..."); procRunner = getAdbRunner(serial, "shell", "rm", "/sdcard/uidump.xml"); try { retCode = procRunner.run(30000); if (retCode != 0) { throw new IOException( "Non-zero return code from \"rm\" xml dump command:\n" + procRunner.getOutputBlob()); } } catch (IOException e) { e.printStackTrace(); showError("Failed to execute \"rm\" xml dump command.", e, monitor); return; } monitor.subTask("Taking UI XML snapshot..."); procRunner = getAdbRunner(serial, "shell", "/system/bin/uiautomator", "dump", "/sdcard/uidump.xml"); try { retCode = procRunner.run(30000); if (retCode != 0) { throw new IOException("Non-zero return code from dump command:\n" + procRunner.getOutputBlob()); } } catch (IOException e) { e.printStackTrace(); showError("Failed to execute dump command.", e, monitor); return; } procRunner = getAdbRunner(serial, "pull", "/sdcard/uidump.xml", xmlDumpFile.getAbsolutePath()); try { retCode = procRunner.run(30000); if (retCode != 0) { throw new IOException("Non-zero return code from pull command:\n" + procRunner.getOutputBlob()); } } catch (IOException e) { e.printStackTrace(); showError("Failed to pull dump file.", e, monitor); return; } monitor.subTask("Deleting old device screenshot..."); procRunner = getAdbRunner(serial, "shell", "rm", "/sdcard/screenshot.png"); try { retCode = procRunner.run(30000); if (retCode != 0) { throw new IOException( "Non-zero return code from \"rm\" screenshot command:\n" + procRunner.getOutputBlob()); } } catch (IOException e) { e.printStackTrace(); showError("Failed to execute \"rm\" screenshot command.", e, monitor); return; } monitor.subTask("Taking device screenshot..."); procRunner = getAdbRunner(serial, "shell", "screencap", "-p", "/sdcard/screenshot.png"); try { retCode = procRunner.run(30000); if (retCode != 0) { throw new IOException("Non-zero return code from screenshot command:\n" + procRunner.getOutputBlob()); } } catch (IOException e) { e.printStackTrace(); showError("Failed to execute screenshot command.", e, monitor); return; } procRunner = getAdbRunner(serial, "pull", "/sdcard/screenshot.png", screenshotFile.getAbsolutePath()); try { retCode = procRunner.run(30000); if (retCode != 0) { throw new IOException("Non-zero return code from pull command:\n" + procRunner.getOutputBlob()); } } catch (IOException e) { e.printStackTrace(); showError("Failed to pull dump file.", e, monitor); return; } final File png = screenshotFile, xml = xmlDumpFile; if(png.length() == 0) { showError("Screenshot file size is 0", null, monitor); return; } else { mViewer.getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { UiAutomatorModel.getModel().loadScreenshotAndXmlDump(png, xml); } }); } monitor.done(); } }); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } /* * Convenience function to construct an 'adb' command, e.g. use 'adb' or 'adb -s NNN' */ private ProcRunner getAdbRunner(String serial, String... command) { List cmd = new ArrayList(); cmd.add("adb"); if (serial != null) { cmd.add("-s"); cmd.add(serial); } for (String s : command) { cmd.add(s); } return new ProcRunner(cmd); } /** * Convenience class to run external process. * * Always redirects stderr into stdout, has timeout control * */ private static class ProcRunner { ProcessBuilder mProcessBuilder; List mOutput = new ArrayList(); public ProcRunner(List command) { mProcessBuilder = new ProcessBuilder(command).redirectErrorStream(true); } public int run(long timeout) throws IOException { final Process p = mProcessBuilder.start(); Thread t = new Thread() { @Override public void run() { String line; mOutput.clear(); try { BufferedReader br = new BufferedReader(new InputStreamReader( p.getInputStream())); while ((line = br.readLine()) != null) { mOutput.add(line); } br.close(); } catch (IOException e) { e.printStackTrace(); } }; }; t.start(); try { t.join(timeout); } catch (InterruptedException e) { e.printStackTrace(); } if (t.isAlive()) { throw new IOException("external process not terminating."); } try { return p.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); throw new IOException(e); } } public String getOutputBlob() { StringBuilder sb = new StringBuilder(); for (String line : mOutput) { sb.append(line); sb.append(System.getProperty("line.separator")); } return sb.toString(); } } }