diff options
author | Konstantin Lopyrev <klopyrev@google.com> | 2010-07-29 17:30:16 -0700 |
---|---|---|
committer | Konstantin Lopyrev <klopyrev@google.com> | 2010-08-17 18:02:55 -0700 |
commit | bc494ce06410728bef8a6fa25f3da361cb66df79 (patch) | |
tree | f22e9a18d1a77def28addfcdd6edf28e2bea8d6a /hierarchyviewer2/libs/hierarchyviewerlib/src/com | |
parent | d127aca14a45d611a8dd1f5ddbb8ea0fe1ef42b8 (diff) | |
download | sdk-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')
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); + } + } +} |