diff options
6 files changed, 234 insertions, 164 deletions
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/CollectingOutputReceiver.java b/ddms/libs/ddmlib/src/com/android/ddmlib/CollectingOutputReceiver.java index cb4612f..80aa8e1 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/CollectingOutputReceiver.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/CollectingOutputReceiver.java @@ -17,16 +17,24 @@ package com.android.ddmlib; import java.io.UnsupportedEncodingException; +import java.util.concurrent.CountDownLatch; /** * A {@link IShellOutputReceiver} which collects the whole shell output into one * {@link String}. */ public class CollectingOutputReceiver implements IShellOutputReceiver { - + private CountDownLatch mCompletionLatch; private StringBuffer mOutputBuffer = new StringBuffer(); private boolean mIsCanceled = false; + public CollectingOutputReceiver() { + } + + public CollectingOutputReceiver(CountDownLatch commandCompleteLatch) { + mCompletionLatch = commandCompleteLatch; + } + public String getOutput() { return mOutputBuffer.toString(); } @@ -68,6 +76,8 @@ public class CollectingOutputReceiver implements IShellOutputReceiver { */ @Override public void flush() { - // ignore + if (mCompletionLatch != null) { + mCompletionLatch.countDown(); + } } } diff --git a/uiautomatorviewer/.classpath b/uiautomatorviewer/.classpath index 4e92f8e..b9c01bb 100644 --- a/uiautomatorviewer/.classpath +++ b/uiautomatorviewer/.classpath @@ -2,6 +2,8 @@ <classpath> <classpathentry excluding="images" kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/> + <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/swt.jar"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.core.commands_3.6.0.I20100512-1500.jar"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.equinox.common_3.6.0.v20100503.jar"/> diff --git a/uiautomatorviewer/Android.mk b/uiautomatorviewer/Android.mk index a5bc768..907718b 100644 --- a/uiautomatorviewer/Android.mk +++ b/uiautomatorviewer/Android.mk @@ -24,6 +24,8 @@ LOCAL_MODULE_TAGS := optional LOCAL_JAVA_LIBRARIES := \ swt \ + sdklib \ + ddmlib \ org.eclipse.jface_3.6.2.M20110210-1200 \ org.eclipse.core.commands_3.6.0.I20100512-1500 \ org.eclipse.equinox.common_3.6.0.v20100503 diff --git a/uiautomatorviewer/src/com/android/uiautomator/DebugBridge.java b/uiautomatorviewer/src/com/android/uiautomator/DebugBridge.java new file mode 100644 index 0000000..09272bc --- /dev/null +++ b/uiautomatorviewer/src/com/android/uiautomator/DebugBridge.java @@ -0,0 +1,76 @@ +/* + * 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; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.IDevice; +import com.android.sdklib.SdkConstants; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +public class DebugBridge { + private static AndroidDebugBridge sDebugBridge; + + private static String getAdbLocation() { + String toolsDir = System.getProperty("com.android.uiautomator.bindir"); //$NON-NLS-1$ + if (toolsDir == null) { + return null; + } + + File sdk = new File(toolsDir).getParentFile(); + + // check if adb is present in platform-tools + File platformTools = new File(sdk, "platform-tools"); + File adb = new File(platformTools, SdkConstants.FN_ADB); + if (adb.exists()) { + return adb.getAbsolutePath(); + } + + // check if adb is present in the tools directory + adb = new File(sdk, SdkConstants.FN_ADB); + if (adb.exists()) { + return adb.getAbsolutePath(); + } + + return null; + } + + public static void init() { + String adbLocation = getAdbLocation(); + if (adbLocation != null) { + AndroidDebugBridge.init(false /* debugger support */); + sDebugBridge = AndroidDebugBridge.createBridge(adbLocation, false); + } + } + + public static void terminate() { + if (sDebugBridge != null) { + sDebugBridge = null; + AndroidDebugBridge.terminate(); + } + } + + public static boolean isInitialized() { + return sDebugBridge != null; + } + + public static List<IDevice> getDevices() { + return Arrays.asList(sDebugBridge.getDevices()); + } +} diff --git a/uiautomatorviewer/src/com/android/uiautomator/UiAutomatorViewer.java b/uiautomatorviewer/src/com/android/uiautomator/UiAutomatorViewer.java index 9f758ae..48e01cf 100644 --- a/uiautomatorviewer/src/com/android/uiautomator/UiAutomatorViewer.java +++ b/uiautomatorviewer/src/com/android/uiautomator/UiAutomatorViewer.java @@ -66,7 +66,6 @@ import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Tree; public class UiAutomatorViewer extends ApplicationWindow { - private static final int IMG_BORDER = 2; private Canvas mScreenshotCanvas; @@ -280,6 +279,8 @@ public class UiAutomatorViewer extends ApplicationWindow { * @param args */ public static void main(String args[]) { + DebugBridge.init(); + try { UiAutomatorViewer window = new UiAutomatorViewer(); window.setBlockOnOpen(true); @@ -287,6 +288,8 @@ public class UiAutomatorViewer extends ApplicationWindow { UiAutomatorModel.getModel().cleanUp(); } catch (Exception e) { e.printStackTrace(); + } finally { + DebugBridge.terminate(); } } diff --git a/uiautomatorviewer/src/com/android/uiautomator/actions/ScreenshotAction.java b/uiautomatorviewer/src/com/android/uiautomator/actions/ScreenshotAction.java index 7d1eaa3..181f655 100644 --- a/uiautomatorviewer/src/com/android/uiautomator/actions/ScreenshotAction.java +++ b/uiautomatorviewer/src/com/android/uiautomator/actions/ScreenshotAction.java @@ -16,6 +16,11 @@ package com.android.uiautomator.actions; +import com.android.ddmlib.CollectingOutputReceiver; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.RawImage; +import com.android.ddmlib.SyncService; +import com.android.uiautomator.DebugBridge; import com.android.uiautomator.UiAutomatorModel; import com.android.uiautomator.UiAutomatorViewer; @@ -23,20 +28,39 @@ 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.Dialog; import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageLoader; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; -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; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; public class ScreenshotAction extends Action { + private static final String UIAUTOMATOR = "/system/bin/uiautomator"; //$NON-NLS-1$ + private static final String UIAUTOMATOR_DUMP_COMMAND = "dump"; //$NON-NLS-1$ + private static final String UIDUMP_DEVICE_PATH = "/sdcard/uidump.xml"; //$NON-NLS-1$ + + private static final int MIN_API_LEVEL = 16; UiAutomatorViewer mViewer; @@ -52,6 +76,18 @@ public class ScreenshotAction extends Action { @Override public void run() { + if (!DebugBridge.isInitialized()) { + MessageDialog.openError(mViewer.getShell(), + "Error obtaining Device Screenshot", + "Unable to connect to adb. Check if adb is installed correctly."); + return; + } + + final IDevice device = pickDevice(); + if (device == null) { + return; + } + ProgressMonitorDialog dialog = new ProgressMonitorDialog(mViewer.getShell()); try { dialog.run(true, false, new IRunnableWithProgress() { @@ -63,7 +99,7 @@ public class ScreenshotAction extends Action { public void run() { Status s = new Status(IStatus.ERROR, "Screenshot", msg, t); ErrorDialog.openError( - mViewer.getShell(), "Error", "Cannot take screenshot", s); + mViewer.getShell(), "Error", "Error obtaining UI hierarchy", s); } }); } @@ -71,12 +107,9 @@ public class ScreenshotAction extends Action { @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(); @@ -91,115 +124,72 @@ public class ScreenshotAction extends Action { } UiAutomatorModel.getModel().registerTempDirectory(tmpDir); - // 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"); + String apiLevelString = device.getProperty(IDevice.PROP_BUILD_API_LEVEL); + int apiLevel; try { - retCode = procRunner.run(30000); - } catch (IOException e) { - e.printStackTrace(); - showError("Failed to detect device", e, monitor); - return; + apiLevel = Integer.parseInt(apiLevelString); + } catch (NumberFormatException e) { + apiLevel = MIN_API_LEVEL; } - 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); + if (apiLevel < MIN_API_LEVEL) { + showError("uiautomator requires a device with API Level " + MIN_API_LEVEL, + null, monitor); return; } + monitor.subTask("Deleting old UI XML snapshot ..."); - procRunner = getAdbRunner(serial, - "shell", "rm", "/sdcard/uidump.xml"); + String command = "rm " + UIDUMP_DEVICE_PATH; 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; + CountDownLatch commandCompleteLatch = new CountDownLatch(1); + device.executeShellCommand(command, + new CollectingOutputReceiver(commandCompleteLatch)); + commandCompleteLatch.await(5, TimeUnit.SECONDS); + } catch (Exception e1) { + // ignore exceptions while deleting stale files } monitor.subTask("Taking UI XML snapshot..."); - procRunner = getAdbRunner(serial, - "shell", "/system/bin/uiautomator", "dump", "/sdcard/uidump.xml"); + command = String.format("%s %s %s", UIAUTOMATOR, + UIAUTOMATOR_DUMP_COMMAND, + UIDUMP_DEVICE_PATH); 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); + CountDownLatch commandCompleteLatch = new CountDownLatch(1); + device.executeShellCommand(command, + new CollectingOutputReceiver(commandCompleteLatch)); + commandCompleteLatch.await(5, TimeUnit.SECONDS); + } catch (Exception e1) { + showError("", e1, monitor); return; } - monitor.subTask("Deleting old device screenshot..."); - procRunner = getAdbRunner(serial, - "shell", "rm", "/sdcard/screenshot.png"); + monitor.subTask("Pull UI XML snapshot from device..."); 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); + device.getSyncService().pullFile(UIDUMP_DEVICE_PATH, + xmlDumpFile.getAbsolutePath(), SyncService.getNullProgressMonitor()); + } catch (Exception e1) { + showError("Error copying UI XML file from device", e1, 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()); + monitor.subTask("Taking screenshot..."); + RawImage rawImage; 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); + rawImage = device.getScreenshot(); + } catch (Exception e1) { + showError("Error taking device screenshot", e1, monitor); return; } + + PaletteData palette = new PaletteData( + rawImage.getRedMask(), + rawImage.getGreenMask(), + rawImage.getBlueMask()); + ImageData imageData = new ImageData(rawImage.width, rawImage.height, + rawImage.bpp, palette, 1, rawImage.data); + ImageLoader loader = new ImageLoader(); + loader.data = new ImageData[] { imageData }; + loader.save(screenshotFile.getAbsolutePath(), SWT.IMAGE_PNG); + final File png = screenshotFile, xml = xmlDumpFile; if(png.length() == 0) { showError("Screenshot file size is 0", null, monitor); @@ -222,81 +212,68 @@ public class ScreenshotAction extends Action { } } - /* - * Convenience function to construct an 'adb' command, e.g. use 'adb' or 'adb -s NNN' - */ - private ProcRunner getAdbRunner(String serial, String... command) { - List<String> cmd = new ArrayList<String>(); - cmd.add("adb"); - if (serial != null) { - cmd.add("-s"); - cmd.add(serial); - } - for (String s : command) { - cmd.add(s); + private IDevice pickDevice() { + List<IDevice> devices = DebugBridge.getDevices(); + if (devices.size() == 0) { + MessageDialog.openError(mViewer.getShell(), + "Error obtaining Device Screenshot", + "No Android devices were detected by adb."); + return null; + } else if (devices.size() == 1) { + return devices.get(0); + } else { + DevicePickerDialog dlg = new DevicePickerDialog(mViewer.getShell(), devices); + if (dlg.open() != Window.OK) { + return null; + } + return dlg.getSelectedDevice(); } - return new ProcRunner(cmd); } - /** - * Convenience class to run external process. - * - * Always redirects stderr into stdout, has timeout control - * - */ - private static class ProcRunner { - - ProcessBuilder mProcessBuilder; + private static class DevicePickerDialog extends Dialog { + private final List<IDevice> mDevices; + private final String[] mDeviceNames; + private static int sSelectedDeviceIndex; - List<String> mOutput = new ArrayList<String>(); + public DevicePickerDialog(Shell parentShell, List<IDevice> devices) { + super(parentShell); - public ProcRunner(List<String> command) { - mProcessBuilder = new ProcessBuilder(command).redirectErrorStream(true); + mDevices = devices; + mDeviceNames = new String[mDevices.size()]; + for (int i = 0; i < devices.size(); i++) { + mDeviceNames[i] = devices.get(i).getName(); + } } - public int run(long timeout) throws IOException { - final Process p = mProcessBuilder.start(); - Thread t = new Thread() { + @Override + protected Control createDialogArea(Composite parentShell) { + Composite parent = (Composite) super.createDialogArea(parentShell); + Composite c = new Composite(parent, SWT.NONE); + + c.setLayout(new GridLayout(2, false)); + + Label l = new Label(c, SWT.NONE); + l.setText("Select device: "); + + final Combo combo = new Combo(c, SWT.BORDER | SWT.READ_ONLY); + combo.setItems(mDeviceNames); + int defaultSelection = + sSelectedDeviceIndex < mDevices.size() ? sSelectedDeviceIndex : 0; + combo.select(defaultSelection); + sSelectedDeviceIndex = defaultSelection; + + combo.addSelectionListener(new SelectionAdapter() { @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 void widgetSelected(SelectionEvent arg0) { + sSelectedDeviceIndex = combo.getSelectionIndex(); + } + }); + + return parent; } - public String getOutputBlob() { - StringBuilder sb = new StringBuilder(); - for (String line : mOutput) { - sb.append(line); - sb.append(System.getProperty("line.separator")); - } - return sb.toString(); + public IDevice getSelectedDevice() { + return mDevices.get(sSelectedDeviceIndex); } } } |