aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ddms/libs/ddmlib/src/com/android/ddmlib/CollectingOutputReceiver.java14
-rw-r--r--uiautomatorviewer/.classpath2
-rw-r--r--uiautomatorviewer/Android.mk2
-rw-r--r--uiautomatorviewer/src/com/android/uiautomator/DebugBridge.java76
-rw-r--r--uiautomatorviewer/src/com/android/uiautomator/UiAutomatorViewer.java5
-rw-r--r--uiautomatorviewer/src/com/android/uiautomator/actions/ScreenshotAction.java299
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);
}
}
}