aboutsummaryrefslogtreecommitdiffstats
path: root/hierarchyviewer2/libs/hierarchyviewerlib/src/com
diff options
context:
space:
mode:
authorKonstantin Lopyrev <klopyrev@google.com>2010-07-29 17:30:16 -0700
committerKonstantin Lopyrev <klopyrev@google.com>2010-08-17 18:02:55 -0700
commitbc494ce06410728bef8a6fa25f3da361cb66df79 (patch)
treef22e9a18d1a77def28addfcdd6edf28e2bea8d6a /hierarchyviewer2/libs/hierarchyviewerlib/src/com
parentd127aca14a45d611a8dd1f5ddbb8ea0fe1ef42b8 (diff)
downloadsdk-bc494ce06410728bef8a6fa25f3da361cb66df79.zip
sdk-bc494ce06410728bef8a6fa25f3da361cb66df79.tar.gz
sdk-bc494ce06410728bef8a6fa25f3da361cb66df79.tar.bz2
Adding pixel perfect view, loupe and tree.
Change-Id: I9be3e9037dec5eeb240608ba8c6329fd77689bbe
Diffstat (limited to 'hierarchyviewer2/libs/hierarchyviewerlib/src/com')
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java11
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java77
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java58
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java186
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java196
5 files changed, 518 insertions, 10 deletions
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java
index 0f463d9..528c35c 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java
@@ -17,6 +17,7 @@
package com.android.hierarchyviewerlib;
import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
+import com.android.hierarchyviewerlib.models.PixelPerfectModel;
/**
* This is the central point for getting access to the various parts of the
@@ -29,6 +30,8 @@ public class ComponentRegistry {
private static DeviceSelectionModel deviceSelectionModel;
+ private static PixelPerfectModel pixelPerfectModel;
+
public static HierarchyViewerDirector getDirector() {
return director;
}
@@ -44,4 +47,12 @@ public class ComponentRegistry {
public static void setDeviceSelectionModel(DeviceSelectionModel deviceSelectionModel) {
ComponentRegistry.deviceSelectionModel = deviceSelectionModel;
}
+
+ public static void setPixelPerfectModel(PixelPerfectModel pixelPerfectModel) {
+ ComponentRegistry.pixelPerfectModel = pixelPerfectModel;
+ }
+
+ public static PixelPerfectModel getPixelPerfectModel() {
+ return pixelPerfectModel;
+ }
}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
index e60a6f2..c21da49 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
@@ -16,15 +16,21 @@
package com.android.hierarchyviewerlib;
+import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
+import com.android.ddmlib.RawImage;
+import com.android.ddmlib.TimeoutException;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.hierarchyviewerlib.device.DeviceBridge;
+import com.android.hierarchyviewerlib.device.ViewNode;
import com.android.hierarchyviewerlib.device.Window;
import com.android.hierarchyviewerlib.device.WindowUpdater;
import com.android.hierarchyviewerlib.device.DeviceBridge.ViewServerInfo;
import com.android.hierarchyviewerlib.device.WindowUpdater.IWindowChangeListener;
+import java.io.IOException;
+
/**
* This is the class where most of the logic resides.
*/
@@ -33,6 +39,8 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
public static final String TAG = "hierarchyviewer";
+ private int pixelPerfectRefreshesInProgress = 0;
+
public void terminate() {
WindowUpdater.terminate();
}
@@ -74,17 +82,19 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
return;
}
}
- ViewServerInfo viewServerInfo = DeviceBridge.loadViewServerInfo(device);
executeInBackground(new Runnable() {
public void run() {
+ ViewServerInfo viewServerInfo = DeviceBridge.loadViewServerInfo(device);
Window[] windows = DeviceBridge.loadWindows(device);
ComponentRegistry.getDeviceSelectionModel().addDevice(device, windows);
+ if (viewServerInfo.protocolVersion >= 3) {
+ WindowUpdater.startListenForWindowChanges(HierarchyViewerDirector.this,
+ device);
+ focusChanged(device);
+ }
}
});
- if (viewServerInfo.protocolVersion >= 3) {
- WindowUpdater.startListenForWindowChanges(this, device);
- focusChanged(device);
- }
+
}
}
@@ -124,5 +134,62 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
focusedWindow);
}
});
+
+ // Some interesting logic here. We don't want to refresh the pixel
+ // perfect view 1000 times in a row if the focus keeps changing. We just
+ // want it to refresh following the last focus change.
+ boolean proceed = false;
+ synchronized (this) {
+ if (pixelPerfectRefreshesInProgress <= 1) {
+ proceed = true;
+ pixelPerfectRefreshesInProgress++;
+ }
+ }
+ if (proceed) {
+ executeInBackground(new Runnable() {
+ public void run() {
+ if (ComponentRegistry.getDeviceSelectionModel().getFocusedWindow(device) != -1
+ && device == ComponentRegistry.getPixelPerfectModel().getDevice()) {
+ try {
+ ViewNode viewNode =
+ DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
+ RawImage screenshot = device.getScreenshot();
+ ComponentRegistry.getPixelPerfectModel().setFocusData(screenshot,
+ viewNode);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to load screenshot from device " + device);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout loading screenshot from device " + device);
+ } catch (AdbCommandRejectedException e) {
+ Log.e(TAG, "Adb rejected command to load screenshot from device "
+ + device);
+ }
+ }
+ synchronized (HierarchyViewerDirector.this) {
+ pixelPerfectRefreshesInProgress--;
+ }
+ }
+
+ });
+ }
+ }
+
+ public void loadPixelPerfectData(final IDevice device) {
+ executeInBackground(new Runnable() {
+ public void run() {
+ try {
+ RawImage screenshot = device.getScreenshot();
+ ViewNode viewNode =
+ DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
+ ComponentRegistry.getPixelPerfectModel().setData(device, screenshot, viewNode);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to load screenshot from device " + device);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout loading screenshot from device " + device);
+ } catch (AdbCommandRejectedException e) {
+ Log.e(TAG, "Adb rejected command to load screenshot from device " + device);
+ }
+ }
+ });
}
}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
index 7a5a6f7..001f98b 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
@@ -263,8 +263,11 @@ public class DeviceBridge {
try {
connection = new DeviceConnection(device);
connection.sendCommand("SERVER");
- server = Integer.parseInt(connection.getInputStream().readLine());
- } catch (IOException e) {
+ String line = connection.getInputStream().readLine();
+ if (line != null) {
+ server = Integer.parseInt(line);
+ }
+ } catch (Exception e) {
Log.e(TAG, "Unable to get view server version from device " + device);
} finally {
if (connection != null) {
@@ -275,8 +278,11 @@ public class DeviceBridge {
try {
connection = new DeviceConnection(device);
connection.sendCommand("PROTOCOL");
- protocol = Integer.parseInt(connection.getInputStream().readLine());
- } catch (IOException e) {
+ String line = connection.getInputStream().readLine();
+ if (line != null) {
+ protocol = Integer.parseInt(line);
+ }
+ } catch (Exception e) {
Log.e(TAG, "Unable to get view server protocol version from device " + device);
} finally {
if (connection != null) {
@@ -369,7 +375,7 @@ public class DeviceBridge {
connection = new DeviceConnection(device);
connection.sendCommand("GET_FOCUS");
String line = connection.getInputStream().readLine();
- if (line.length() == 0) {
+ if (line == null || line.length() == 0) {
return -1;
}
return (int) Long.parseLong(line.substring(0, line.indexOf(' ')), 16);
@@ -382,4 +388,46 @@ public class DeviceBridge {
}
return -1;
}
+
+ public static ViewNode loadWindowData(Window window) {
+ DeviceConnection connection = null;
+ try {
+ connection = new DeviceConnection(window.getDevice());
+ connection.sendCommand("DUMP " + window.encode());
+ BufferedReader in = connection.getInputStream();
+ ViewNode currentNode = null;
+ int currentDepth = -1;
+ String line;
+ while ((line = in.readLine()) != null) {
+ if ("DONE.".equalsIgnoreCase(line)) {
+ break;
+ }
+ int depth = 0;
+ while (line.charAt(depth) == ' ') {
+ depth++;
+ }
+ while (depth <= currentDepth) {
+ currentNode = currentNode.parent;
+ currentDepth--;
+ }
+ currentNode = new ViewNode(currentNode, line.substring(depth));
+ currentDepth = depth;
+ }
+ if (currentNode == null) {
+ return null;
+ }
+ while (currentNode.parent != null) {
+ currentNode = currentNode.parent;
+ }
+ return currentNode;
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to load window data for window " + window.getTitle() + " on device "
+ + window.getDevice());
+ } finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ return null;
+ }
}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java
new file mode 100644
index 0000000..00453ee
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2008 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.hierarchyviewerlib.device;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ViewNode {
+ public String id;
+
+ public String name;
+
+ public String hashCode;
+
+ public List<Property> properties = new ArrayList<Property>();
+
+ public Map<String, Property> namedProperties = new HashMap<String, Property>();
+
+ public ViewNode parent;
+
+ public List<ViewNode> children = new ArrayList<ViewNode>();
+
+ public int left;
+
+ public int top;
+
+ public int width;
+
+ public int height;
+
+ public int scrollX;
+
+ public int scrollY;
+
+ public int paddingLeft;
+
+ public int paddingRight;
+
+ public int paddingTop;
+
+ public int paddingBottom;
+
+ public int marginLeft;
+
+ public int marginRight;
+
+ public int marginTop;
+
+ public int marginBottom;
+
+ public int baseline;
+
+ public boolean willNotDraw;
+
+ public boolean hasMargins;
+
+ public boolean hasFocus;
+
+ public int index;
+
+ public ViewNode(ViewNode parent, String data) {
+ this.parent = parent;
+ index = this.parent == null ? 0 : this.parent.children.size();
+ if (this.parent != null) {
+ this.parent.children.add(this);
+ }
+ int delimIndex = data.indexOf('@');
+ name = data.substring(0, delimIndex);
+ data = data.substring(delimIndex + 1);
+ delimIndex = data.indexOf(' ');
+ hashCode = data.substring(0, delimIndex);
+ loadProperties(data.substring(delimIndex + 1).trim());
+ }
+
+ private void loadProperties(String data) {
+ int start = 0;
+ boolean stop;
+ do {
+ int index = data.indexOf('=', start);
+ ViewNode.Property property = new ViewNode.Property();
+ property.name = data.substring(start, index);
+
+ int index2 = data.indexOf(',', index + 1);
+ int length = Integer.parseInt(data.substring(index + 1, index2));
+ start = index2 + 1 + length;
+ property.value = data.substring(index2 + 1, index2 + 1 + length);
+
+ properties.add(property);
+ namedProperties.put(property.name, property);
+
+ stop = start >= data.length();
+ if (!stop) {
+ start += 1;
+ }
+ } while (!stop);
+
+ Collections.sort(properties, new Comparator<ViewNode.Property>() {
+ public int compare(ViewNode.Property source, ViewNode.Property destination) {
+ return source.name.compareTo(destination.name);
+ }
+ });
+
+ id = namedProperties.get("mID").value;
+
+ left = getInt("mLeft", 0);
+ top = getInt("mTop", 0);
+ width = getInt("getWidth()", 0);
+ height = getInt("getHeight()", 0);
+ scrollX = getInt("mScrollX", 0);
+ scrollY = getInt("mScrollY", 0);
+ paddingLeft = getInt("mPaddingLeft", 0);
+ paddingRight = getInt("mPaddingRight", 0);
+ paddingTop = getInt("mPaddingTop", 0);
+ paddingBottom = getInt("mPaddingBottom", 0);
+ marginLeft = getInt("layout_leftMargin", Integer.MIN_VALUE);
+ marginRight = getInt("layout_rightMargin", Integer.MIN_VALUE);
+ marginTop = getInt("layout_topMargin", Integer.MIN_VALUE);
+ marginBottom = getInt("layout_bottomMargin", Integer.MIN_VALUE);
+ baseline = getInt("getBaseline()", 0);
+ willNotDraw = getBoolean("willNotDraw()", false);
+ hasFocus = getBoolean("hasFocus()", false);
+
+ hasMargins =
+ marginLeft != Integer.MIN_VALUE && marginRight != Integer.MIN_VALUE
+ && marginTop != Integer.MIN_VALUE && marginBottom != Integer.MIN_VALUE;
+
+ }
+
+ private boolean getBoolean(String name, boolean defaultValue) {
+ Property p = namedProperties.get(name);
+ if (p != null) {
+ try {
+ return Boolean.parseBoolean(p.value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ private int getInt(String name, int defaultValue) {
+ Property p = namedProperties.get(name);
+ if (p != null) {
+ try {
+ return Integer.parseInt(p.value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ return name + "@" + hashCode;
+ }
+
+ public static class Property {
+ public String name;
+
+ public String value;
+
+ @Override
+ public String toString() {
+ return name + '=' + value;
+ }
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java
new file mode 100644
index 0000000..6963b25
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2010 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.hierarchyviewerlib.models;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.RawImage;
+import com.android.hierarchyviewerlib.device.ViewNode;
+
+import java.util.ArrayList;
+
+public class PixelPerfectModel {
+
+ public static class Point {
+ public int x;
+
+ public int y;
+
+ Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ private IDevice device;
+
+ private RawImage image;
+
+ private Point crosshairLocation;
+
+ private ViewNode viewNode;
+
+ private ViewNode selected;
+
+ private final ArrayList<ImageChangeListener> imageChangeListeners =
+ new ArrayList<ImageChangeListener>();
+
+ public void setData(IDevice device, RawImage image, ViewNode viewNode) {
+ synchronized (this) {
+ this.device = device;
+ this.image = image;
+ this.viewNode = viewNode;
+ this.crosshairLocation = new Point(image.width / 2, image.height / 2);
+ this.selected = null;
+ }
+ notifyImageLoaded();
+ }
+
+ public void setCrosshairLocation(int x, int y) {
+ synchronized (this) {
+ crosshairLocation = new Point(x, y);
+ }
+ notifyCrosshairMoved();
+ }
+
+ public void setSelected(ViewNode selected) {
+ synchronized (this) {
+ this.selected = selected;
+ }
+ notifySelectionChanged();
+ }
+
+ public void setFocusData(RawImage image, ViewNode viewNode) {
+ synchronized (this) {
+ this.image = image;
+ this.viewNode = viewNode;
+ this.selected = null;
+ }
+ notifyFocusChanged();
+ }
+
+ public ViewNode getViewNode() {
+ synchronized (this) {
+ return viewNode;
+ }
+ }
+
+ public Point getCrosshairLocation() {
+ synchronized (this) {
+ return crosshairLocation;
+ }
+ }
+
+ public RawImage getImage() {
+ synchronized (this) {
+ return image;
+ }
+ }
+
+ public ViewNode getSelected() {
+ synchronized (this) {
+ return selected;
+ }
+ }
+
+ public IDevice getDevice() {
+ synchronized (this) {
+ return device;
+ }
+ }
+
+ public static interface ImageChangeListener {
+ public void imageLoaded();
+
+ public void imageChanged();
+
+ public void crosshairMoved();
+
+ public void selectionChanged();
+
+ public void focusChanged();
+ }
+
+ private ImageChangeListener[] getImageChangeListenerList() {
+ ImageChangeListener[] listeners = null;
+ synchronized (imageChangeListeners) {
+ if (imageChangeListeners.size() == 0) {
+ return null;
+ }
+ listeners =
+ imageChangeListeners.toArray(new ImageChangeListener[imageChangeListeners
+ .size()]);
+ }
+ return listeners;
+ }
+
+ public void notifyImageLoaded() {
+ ImageChangeListener[] listeners = getImageChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].imageLoaded();
+ }
+ }
+ }
+
+ public void notifyImageChanged() {
+ ImageChangeListener[] listeners = getImageChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].imageChanged();
+ }
+ }
+ }
+
+ public void notifyCrosshairMoved() {
+ ImageChangeListener[] listeners = getImageChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].crosshairMoved();
+ }
+ }
+ }
+
+ public void notifySelectionChanged() {
+ ImageChangeListener[] listeners = getImageChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].selectionChanged();
+ }
+ }
+ }
+
+ public void notifyFocusChanged() {
+ ImageChangeListener[] listeners = getImageChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].focusChanged();
+ }
+ }
+ }
+
+ public void addImageChangeListener(ImageChangeListener listener) {
+ synchronized (imageChangeListeners) {
+ imageChangeListeners.add(listener);
+ }
+ }
+
+ public void removeImageChangeListener(ImageChangeListener listener) {
+ synchronized (imageChangeListeners) {
+ imageChangeListeners.remove(listener);
+ }
+ }
+}