aboutsummaryrefslogtreecommitdiffstats
path: root/hierarchyviewer2/libs/hierarchyviewerlib/src
diff options
context:
space:
mode:
Diffstat (limited to 'hierarchyviewer2/libs/hierarchyviewerlib/src')
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk1
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java47
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java128
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/Test.java3
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java365
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java89
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/Window.java77
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/WindowUpdater.java156
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java224
9 files changed, 1087 insertions, 3 deletions
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
index 77808b1..1be1a29 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
@@ -18,6 +18,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := ddmlib
+
LOCAL_MODULE := hierarchyviewerlib
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java
new file mode 100644
index 0000000..0f463d9
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
+
+/**
+ * This is the central point for getting access to the various parts of the
+ * Hierarchy Viewer. Components register themselves with the class using the
+ * setters and can be accessed using the getters.
+ */
+public class ComponentRegistry {
+
+ private static HierarchyViewerDirector director;
+
+ private static DeviceSelectionModel deviceSelectionModel;
+
+ public static HierarchyViewerDirector getDirector() {
+ return director;
+ }
+
+ public static void setDirector(HierarchyViewerDirector director) {
+ ComponentRegistry.director = director;
+ }
+
+ public static DeviceSelectionModel getDeviceSelectionModel() {
+ return deviceSelectionModel;
+ }
+
+ public static void setDeviceSelectionModel(DeviceSelectionModel deviceSelectionModel) {
+ ComponentRegistry.deviceSelectionModel = deviceSelectionModel;
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
new file mode 100644
index 0000000..e60a6f2
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
@@ -0,0 +1,128 @@
+/*
+ * 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;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.hierarchyviewerlib.device.DeviceBridge;
+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;
+
+/**
+ * This is the class where most of the logic resides.
+ */
+public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
+ IWindowChangeListener {
+
+ public static final String TAG = "hierarchyviewer";
+
+ public void terminate() {
+ WindowUpdater.terminate();
+ }
+
+ public abstract String getAdbLocation();
+
+ public void initDebugBridge() {
+ DeviceBridge.initDebugBridge(getAdbLocation());
+ }
+
+ public void stopDebugBridge() {
+ DeviceBridge.terminate();
+ }
+
+ public void populateDeviceSelectionModel() {
+ IDevice[] devices = DeviceBridge.getDevices();
+ for (IDevice device : devices) {
+ deviceConnected(device);
+ }
+ }
+
+ public void startListenForDevices() {
+ DeviceBridge.startListenForDevices(this);
+ }
+
+ public void stopListenForDevices() {
+ DeviceBridge.stopListenForDevices(this);
+ }
+
+ public abstract void executeInBackground(Runnable task);
+
+ public void deviceConnected(final IDevice device) {
+ if (device.isOnline()) {
+ DeviceBridge.setupDeviceForward(device);
+ if (!DeviceBridge.isViewServerRunning(device)) {
+ if (!DeviceBridge.startViewServer(device)) {
+ DeviceBridge.removeDeviceForward(device);
+ Log.e(TAG, "Unable to debug device " + device);
+ return;
+ }
+ }
+ ViewServerInfo viewServerInfo = DeviceBridge.loadViewServerInfo(device);
+ executeInBackground(new Runnable() {
+ public void run() {
+ Window[] windows = DeviceBridge.loadWindows(device);
+ ComponentRegistry.getDeviceSelectionModel().addDevice(device, windows);
+ }
+ });
+ if (viewServerInfo.protocolVersion >= 3) {
+ WindowUpdater.startListenForWindowChanges(this, device);
+ focusChanged(device);
+ }
+ }
+ }
+
+ public void deviceDisconnected(IDevice device) {
+ ViewServerInfo viewServerInfo = DeviceBridge.getViewServerInfo(device);
+ if (viewServerInfo == null) {
+ return;
+ }
+ if (viewServerInfo.protocolVersion >= 3) {
+ WindowUpdater.stopListenForWindowChanges(this, device);
+ }
+ DeviceBridge.removeDeviceForward(device);
+ DeviceBridge.removeViewServerInfo(device);
+ ComponentRegistry.getDeviceSelectionModel().removeDevice(device);
+ }
+
+ public void deviceChanged(IDevice device, int changeMask) {
+ if ((changeMask & IDevice.CHANGE_STATE) != 0 && device.isOnline()) {
+ deviceConnected(device);
+ }
+ }
+
+ public void windowsChanged(final IDevice device) {
+ executeInBackground(new Runnable() {
+ public void run() {
+ Window[] windows = DeviceBridge.loadWindows(device);
+ ComponentRegistry.getDeviceSelectionModel().updateDevice(device, windows);
+ }
+ });
+ }
+
+ public void focusChanged(final IDevice device) {
+ executeInBackground(new Runnable() {
+ public void run() {
+ int focusedWindow = DeviceBridge.getFocusedWindow(device);
+ ComponentRegistry.getDeviceSelectionModel().updateFocusedWindow(device,
+ focusedWindow);
+ }
+ });
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/Test.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/Test.java
deleted file mode 100644
index ad6db3d..0000000
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/Test.java
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.android.hierarchyviewerlib;
-public class Test {
-} \ No newline at end of file
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
new file mode 100644
index 0000000..4edf67f
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
@@ -0,0 +1,365 @@
+/*
+ * 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.device;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.TimeoutException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A bridge to the device.
+ */
+public class DeviceBridge {
+
+ public static final String TAG = "hierarchyviewer";
+
+ private static final int DEFAULT_SERVER_PORT = 4939;
+
+ // These codes must match the auto-generated codes in IWindowManager.java
+ // See IWindowManager.aidl as well
+ private static final int SERVICE_CODE_START_SERVER = 1;
+
+ private static final int SERVICE_CODE_STOP_SERVER = 2;
+
+ private static final int SERVICE_CODE_IS_SERVER_RUNNING = 3;
+
+ private static AndroidDebugBridge bridge;
+
+ private static final HashMap<IDevice, Integer> devicePortMap = new HashMap<IDevice, Integer>();
+
+ private static final HashMap<IDevice, ViewServerInfo> viewServerInfo =
+ new HashMap<IDevice, ViewServerInfo>();
+
+ private static int nextLocalPort = DEFAULT_SERVER_PORT;
+
+ public static class ViewServerInfo {
+ public final int protocolVersion;
+
+ public final int serverVersion;
+
+ ViewServerInfo(int serverVersion, int protocolVersion) {
+ this.protocolVersion = protocolVersion;
+ this.serverVersion = serverVersion;
+ }
+ }
+
+ public static void initDebugBridge(String adbLocation) {
+ if (bridge == null) {
+ AndroidDebugBridge.init(false /* debugger support */);
+ }
+ if (bridge == null || !bridge.isConnected()) {
+ bridge = AndroidDebugBridge.createBridge(adbLocation, true);
+ }
+ }
+
+ public static void terminate() {
+ AndroidDebugBridge.terminate();
+ }
+
+ public static IDevice[] getDevices() {
+ return bridge.getDevices();
+ }
+
+ /*
+ * This adds a listener to the debug bridge. The listener is notified of
+ * connecting/disconnecting devices, devices coming online, etc.
+ */
+ public static void startListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener) {
+ AndroidDebugBridge.addDeviceChangeListener(listener);
+ }
+
+ public static void stopListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener) {
+ AndroidDebugBridge.removeDeviceChangeListener(listener);
+ }
+
+ /**
+ * Sets up a just-connected device to work with the view server.
+ * <p/>
+ * This starts a port forwarding between a local port and a port on the
+ * device.
+ *
+ * @param device
+ */
+ public static void setupDeviceForward(IDevice device) {
+ synchronized (devicePortMap) {
+ if (device.getState() == IDevice.DeviceState.ONLINE) {
+ int localPort = nextLocalPort++;
+ try {
+ device.createForward(localPort, DEFAULT_SERVER_PORT);
+ devicePortMap.put(device, localPort);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout setting up port forwarding for " + device);
+ } catch (AdbCommandRejectedException e) {
+ Log.e(TAG, String.format("Adb rejected forward command for device %1$s: %2$s",
+ device, e.getMessage()));
+ } catch (IOException e) {
+ Log.e(TAG, String.format("Failed to create forward for device %1$s: %2$s",
+ device, e.getMessage()));
+ }
+ }
+ }
+ }
+
+ public static void removeDeviceForward(IDevice device) {
+ synchronized (devicePortMap) {
+ final Integer localPort = devicePortMap.get(device);
+ if (localPort != null) {
+ try {
+ device.removeForward(localPort, DEFAULT_SERVER_PORT);
+ devicePortMap.remove(device);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout removing port forwarding for " + device);
+ } catch (AdbCommandRejectedException e) {
+ // In this case, we want to fail silently.
+ } catch (IOException e) {
+ Log.e(TAG, String.format("Failed to remove forward for device %1$s: %2$s",
+ device, e.getMessage()));
+ }
+ }
+ }
+ }
+
+ public static int getDeviceLocalPort(IDevice device) {
+ synchronized (devicePortMap) {
+ Integer port = devicePortMap.get(device);
+ if (port != null) {
+ return port;
+ }
+
+ Log.e(TAG, "Missing forwarded port for " + device.getSerialNumber());
+ return -1;
+ }
+
+ }
+
+ public static boolean isViewServerRunning(IDevice device) {
+ final boolean[] result = new boolean[1];
+ try {
+ if (device.isOnline()) {
+ device.executeShellCommand(buildIsServerRunningShellCommand(),
+ new BooleanResultReader(result));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to check status of view server on device " + device);
+ }
+ return result[0];
+ }
+
+ public static boolean startViewServer(IDevice device) {
+ return startViewServer(device, DEFAULT_SERVER_PORT);
+ }
+
+ public static boolean startViewServer(IDevice device, int port) {
+ final boolean[] result = new boolean[1];
+ try {
+ if (device.isOnline()) {
+ device.executeShellCommand(buildStartServerShellCommand(port),
+ new BooleanResultReader(result));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to start view server on device " + device);
+ }
+ return result[0];
+ }
+
+ public static boolean stopViewServer(IDevice device) {
+ final boolean[] result = new boolean[1];
+ try {
+ if (device.isOnline()) {
+ device.executeShellCommand(buildStopServerShellCommand(), new BooleanResultReader(
+ result));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to stop view server on device " + device);
+ }
+ return result[0];
+ }
+
+ private static String buildStartServerShellCommand(int port) {
+ return String.format("service call window %d i32 %d", SERVICE_CODE_START_SERVER, port);
+ }
+
+ private static String buildStopServerShellCommand() {
+ return String.format("service call window %d", SERVICE_CODE_STOP_SERVER);
+ }
+
+ private static String buildIsServerRunningShellCommand() {
+ return String.format("service call window %d", SERVICE_CODE_IS_SERVER_RUNNING);
+ }
+
+ private static class BooleanResultReader extends MultiLineReceiver {
+ private final boolean[] mResult;
+
+ public BooleanResultReader(boolean[] result) {
+ mResult = result;
+ }
+
+ @Override
+ public void processNewLines(String[] strings) {
+ if (strings.length > 0) {
+ Pattern pattern = Pattern.compile(".*?\\([0-9]{8} ([0-9]{8}).*");
+ Matcher matcher = pattern.matcher(strings[0]);
+ if (matcher.matches()) {
+ if (Integer.parseInt(matcher.group(1)) == 1) {
+ mResult[0] = true;
+ }
+ }
+ }
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ public static ViewServerInfo loadViewServerInfo(IDevice device) {
+ int server = 2;
+ int protocol = 2;
+ DeviceConnection connection = null;
+ try {
+ connection = new DeviceConnection(device);
+ connection.sendCommand("SERVER");
+ server = Integer.parseInt(connection.getInputStream().readLine());
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to get view server version from device " + device);
+ } finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ connection = null;
+ try {
+ connection = new DeviceConnection(device);
+ connection.sendCommand("PROTOCOL");
+ protocol = Integer.parseInt(connection.getInputStream().readLine());
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to get view server protocol version from device " + device);
+ } finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ ViewServerInfo returnValue = new ViewServerInfo(server, protocol);
+ synchronized (viewServerInfo) {
+ viewServerInfo.put(device, returnValue);
+ }
+ return returnValue;
+ }
+
+ public static ViewServerInfo getViewServerInfo(IDevice device) {
+ synchronized (viewServerInfo) {
+ return viewServerInfo.get(device);
+ }
+ }
+
+ public static void removeViewServerInfo(IDevice device) {
+ synchronized (viewServerInfo) {
+ viewServerInfo.remove(device);
+ }
+ }
+
+ /*
+ * This loads the list of windows from the specified device. The format is:
+ * hashCode1 title1 hashCode2 title2 ... hashCodeN titleN DONE.
+ */
+ public static Window[] loadWindows(IDevice device) {
+ ArrayList<Window> windows = new ArrayList<Window>();
+ DeviceConnection connection = null;
+ ViewServerInfo serverInfo = getViewServerInfo(device);
+ try {
+ connection = new DeviceConnection(device);
+ connection.sendCommand("LIST");
+ BufferedReader in = connection.getInputStream();
+ String line;
+ while ((line = in.readLine()) != null) {
+ if ("DONE.".equalsIgnoreCase(line)) {
+ break;
+ }
+
+ int index = line.indexOf(' ');
+ if (index != -1) {
+ String windowId = line.substring(0, index);
+
+ int id;
+ if (serverInfo.serverVersion > 2) {
+ id = (int) Long.parseLong(windowId, 16);
+ } else {
+ id = Integer.parseInt(windowId, 16);
+ }
+
+ Window w = new Window(device, line.substring(index + 1), id);
+ windows.add(w);
+ }
+ }
+ // Automatic refreshing of windows was added in protocol version 3.
+ // Before, the user needed to specify explicitely that he wants to
+ // get the focused window, which was done using a special type of
+ // window with hash code -1.
+ if (serverInfo.protocolVersion < 3) {
+ windows.add(Window.getFocusedWindow(device));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to load the window list from device " + device);
+ } finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ // The server returns the list of windows from the window at the bottom
+ // to the top. We want the reverse order to put the top window on top of
+ // the list.
+ Window[] returnValue = new Window[windows.size()];
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ returnValue[returnValue.length - i - 1] = windows.get(i);
+ }
+ return returnValue;
+ }
+
+ /*
+ * This gets the hash code of the window that has focus. Only works with
+ * protocol version 3 and above.
+ */
+ public static int getFocusedWindow(IDevice device) {
+ DeviceConnection connection = null;
+ try {
+ connection = new DeviceConnection(device);
+ connection.sendCommand("GET_FOCUS");
+ String line = connection.getInputStream().readLine();
+ if (line.length() == 0) {
+ return -1;
+ }
+ return (int) Long.parseLong(line.substring(0, line.indexOf(' ')), 16);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to get the focused window from device " + device);
+ } finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ return -1;
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java
new file mode 100644
index 0000000..581f76b
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java
@@ -0,0 +1,89 @@
+/*
+ * 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.device;
+
+import com.android.ddmlib.IDevice;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+
+/**
+ * This class is used for connecting to a device in debug mode running the view
+ * server.
+ */
+public class DeviceConnection {
+
+ // Now a socket channel, since socket channels are friendly with interrupts.
+ private SocketChannel socketChannel;
+
+ private BufferedReader in;
+
+ private BufferedWriter out;
+
+ public DeviceConnection(IDevice device) throws IOException {
+ socketChannel = SocketChannel.open();
+ socketChannel.connect(new InetSocketAddress("127.0.0.1", DeviceBridge
+ .getDeviceLocalPort(device)));
+ }
+
+ public BufferedReader getInputStream() throws IOException {
+ if (in == null) {
+ in = new BufferedReader(new InputStreamReader(socketChannel.socket().getInputStream()));
+ }
+ return in;
+ }
+
+ public BufferedWriter getOutputStream() throws IOException {
+ if (out == null) {
+ out =
+ new BufferedWriter(new OutputStreamWriter(socketChannel.socket()
+ .getOutputStream()));
+ }
+ return out;
+ }
+
+ public void sendCommand(String command) throws IOException {
+ BufferedWriter out = getOutputStream();
+ out.write(command);
+ out.newLine();
+ out.flush();
+ }
+
+ public void close() {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ }
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ }
+ try {
+ socketChannel.close();
+ } catch (IOException e) {
+ }
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/Window.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/Window.java
new file mode 100644
index 0000000..1869a51
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/Window.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.android.ddmlib.IDevice;
+
+/**
+ * Used for storing a window from the window manager service on the device.
+ * These are the windows that the device selector shows.
+ */
+public class Window {
+
+ private String title;
+
+ private int hashCode;
+
+ private IDevice device;
+
+ public Window(IDevice device, String title, int hashCode) {
+ this.device = device;
+ this.title = title;
+ this.hashCode = hashCode;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public int getHashCode() {
+ return hashCode;
+ }
+
+ public String encode() {
+ return Integer.toHexString(hashCode);
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ public static Window getFocusedWindow(IDevice device) {
+ return new Window(device, "<Focused Window>", -1);
+ }
+
+ /*
+ * After each refresh of the windows in the device selector, the windows are
+ * different instances and automatically reselecting the same window doesn't
+ * work in the device selector unless the equals method is defined here.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Window) {
+ return hashCode == ((Window) other).hashCode
+ && device.getSerialNumber().equals(((Window) other).device.getSerialNumber());
+ }
+ return false;
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/WindowUpdater.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/WindowUpdater.java
new file mode 100644
index 0000000..26797d2
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/WindowUpdater.java
@@ -0,0 +1,156 @@
+/*
+ * 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.device;
+
+import com.android.ddmlib.IDevice;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class handles automatic updating of the list of windows in the device
+ * selector for device with protocol version 3 or above of the view server. It
+ * connects to the devices, keeps the connection open and listens for messages.
+ * It notifies all it's listeners of changes.
+ */
+public class WindowUpdater {
+ private static HashMap<IDevice, ArrayList<IWindowChangeListener>> windowChangeListeners =
+ new HashMap<IDevice, ArrayList<IWindowChangeListener>>();
+
+ private static HashMap<IDevice, Thread> listeningThreads = new HashMap<IDevice, Thread>();
+
+ public static interface IWindowChangeListener {
+ public void windowsChanged(IDevice device);
+
+ public void focusChanged(IDevice device);
+ }
+
+ public static void terminate() {
+ synchronized (listeningThreads) {
+ for (IDevice device : listeningThreads.keySet()) {
+ listeningThreads.get(device).interrupt();
+
+ }
+ }
+ }
+
+ public static void startListenForWindowChanges(IWindowChangeListener listener, IDevice device) {
+ synchronized (windowChangeListeners) {
+ // In this case, a listening thread already exists, so we don't need
+ // to create another one.
+ if (windowChangeListeners.containsKey(device)) {
+ windowChangeListeners.get(device).add(listener);
+ return;
+ }
+ ArrayList<IWindowChangeListener> listeners = new ArrayList<IWindowChangeListener>();
+ listeners.add(listener);
+ windowChangeListeners.put(device, listeners);
+ }
+ // Start listening
+ Thread listeningThread = new Thread(new WindowChangeMonitor(device));
+ synchronized (listeningThreads) {
+ listeningThreads.put(device, listeningThread);
+ }
+ listeningThread.start();
+ }
+
+ public static void stopListenForWindowChanges(IWindowChangeListener listener, IDevice device) {
+ synchronized (windowChangeListeners) {
+ ArrayList<IWindowChangeListener> listeners = windowChangeListeners.get(device);
+ listeners.remove(listener);
+ // There are more listeners, so don't stop the listening thread.
+ if (listeners.size() != 0) {
+ return;
+ }
+ windowChangeListeners.remove(device);
+ }
+ // Everybody left, so the party's over!
+ Thread listeningThread;
+ synchronized (listeningThreads) {
+ listeningThread = listeningThreads.get(device);
+ listeningThreads.remove(device);
+ }
+ listeningThread.interrupt();
+ }
+
+ private static IWindowChangeListener[] getWindowChangeListenersAsArray(IDevice device) {
+ IWindowChangeListener[] listeners;
+ synchronized (windowChangeListeners) {
+ ArrayList<IWindowChangeListener> windowChangeListenerList =
+ windowChangeListeners.get(device);
+ if (windowChangeListenerList == null) {
+ return null;
+ }
+ listeners =
+ windowChangeListenerList
+ .toArray(new IWindowChangeListener[windowChangeListenerList.size()]);
+ }
+ return listeners;
+ }
+
+ public static void notifyWindowsChanged(IDevice device) {
+ IWindowChangeListener[] listeners = getWindowChangeListenersAsArray(device);
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].windowsChanged(device);
+ }
+ }
+ }
+
+ public static void notifyFocusChanged(IDevice device) {
+ IWindowChangeListener[] listeners = getWindowChangeListenersAsArray(device);
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].focusChanged(device);
+ }
+ }
+ }
+
+ private static class WindowChangeMonitor implements Runnable {
+ private IDevice device;
+
+ public WindowChangeMonitor(IDevice device) {
+ this.device = device;
+ }
+
+ public void run() {
+ while (!Thread.currentThread().isInterrupted()) {
+ DeviceConnection connection = null;
+ try {
+ connection = new DeviceConnection(device);
+ connection.sendCommand("AUTOLIST");
+ String line;
+ while (!Thread.currentThread().isInterrupted()
+ && (line = connection.getInputStream().readLine()) != null) {
+ if (line.equalsIgnoreCase("LIST UPDATE")) {
+ notifyWindowsChanged(device);
+ } else if (line.equalsIgnoreCase("FOCUS UPDATE")) {
+ notifyFocusChanged(device);
+ }
+ }
+
+ } catch (IOException e) {
+ } finally {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java
new file mode 100644
index 0000000..7c4f2f6
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java
@@ -0,0 +1,224 @@
+/*
+ * 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.hierarchyviewerlib.device.Window;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class stores the list of windows for each connected device. It notifies
+ * listeners of any changes as well as knows which window is currently selected
+ * in the device selector.
+ */
+public class DeviceSelectionModel {
+
+ private final HashMap<IDevice, Window[]> deviceMap = new HashMap<IDevice, Window[]>();
+
+ private final HashMap<IDevice, Integer> focusedWindowHashes = new HashMap<IDevice, Integer>();
+
+ private final ArrayList<IDevice> deviceList = new ArrayList<IDevice>();
+
+ private final ArrayList<WindowChangeListener> windowChangeListeners =
+ new ArrayList<WindowChangeListener>();
+
+ private IDevice selectedDevice;
+
+ private Window selectedWindow;
+
+ public void addDevice(IDevice device, Window[] windows) {
+ synchronized (deviceMap) {
+ deviceMap.put(device, windows);
+ deviceList.add(device);
+ }
+ notifyDeviceConnected(device);
+ }
+
+ public void removeDevice(IDevice device) {
+ synchronized (deviceMap) {
+ deviceMap.remove(device);
+ deviceList.remove(device);
+ focusedWindowHashes.remove(device);
+ if (selectedDevice == device) {
+ selectedDevice = null;
+ selectedWindow = null;
+ }
+ }
+ notifyDeviceDisconnected(device);
+ }
+
+ public void updateDevice(IDevice device, Window[] windows) {
+ synchronized (deviceMap) {
+ deviceMap.put(device, windows);
+ // If the selected window no longer exists, we clear the selection.
+ if (selectedDevice == device) {
+ boolean windowStillExists = false;
+ for (int i = 0; i < windows.length && !windowStillExists; i++) {
+ if (windows[i].equals(selectedWindow)) {
+ windowStillExists = true;
+ }
+ }
+ if (!windowStillExists) {
+ selectedDevice = null;
+ selectedWindow = null;
+ }
+ }
+ }
+ notifyDeviceChanged(device);
+ }
+
+ /*
+ * Change which window has focus and notify the listeners.
+ */
+ public void updateFocusedWindow(IDevice device, int focusedWindow) {
+ Integer oldValue = null;
+ synchronized (deviceMap) {
+ // A value of -1 means that no window has focus. This is a strange
+ // transitive state in the window manager service.
+ if (focusedWindow == -1) {
+ oldValue = focusedWindowHashes.remove(device);
+ } else {
+ oldValue = focusedWindowHashes.put(device, new Integer(focusedWindow));
+ }
+ }
+ // Only notify if the values are different. It would be cool if Java
+ // containers accepted basic types like int.
+ if ((oldValue == null && focusedWindow != -1)
+ || (oldValue != null && oldValue.intValue() != focusedWindow)) {
+ notifyFocusChanged(device);
+ }
+ }
+
+ public static interface WindowChangeListener {
+ public void deviceConnected(IDevice device);
+
+ public void deviceChanged(IDevice device);
+
+ public void deviceDisconnected(IDevice device);
+
+ public void focusChanged(IDevice device);
+ }
+
+ private WindowChangeListener[] getWindowChangeListenerList() {
+ WindowChangeListener[] listeners = null;
+ synchronized (windowChangeListeners) {
+ if (windowChangeListeners.size() == 0) {
+ return null;
+ }
+ listeners =
+ windowChangeListeners.toArray(new WindowChangeListener[windowChangeListeners
+ .size()]);
+ }
+ return listeners;
+ }
+
+ private void notifyDeviceConnected(IDevice device) {
+ WindowChangeListener[] listeners = getWindowChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].deviceConnected(device);
+ }
+ }
+ }
+
+ private void notifyDeviceChanged(IDevice device) {
+ WindowChangeListener[] listeners = getWindowChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].deviceChanged(device);
+ }
+ }
+ }
+
+ private void notifyDeviceDisconnected(IDevice device) {
+ WindowChangeListener[] listeners = getWindowChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].deviceDisconnected(device);
+ }
+ }
+ }
+
+ private void notifyFocusChanged(IDevice device) {
+ WindowChangeListener[] listeners = getWindowChangeListenerList();
+ if (listeners != null) {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].focusChanged(device);
+ }
+ }
+ }
+
+ public void addWindowChangeListener(WindowChangeListener listener) {
+ synchronized (windowChangeListeners) {
+ windowChangeListeners.add(listener);
+ }
+ }
+
+ public void removeWindowChangeListener(WindowChangeListener listener) {
+ synchronized (windowChangeListeners) {
+ windowChangeListeners.remove(listener);
+ }
+ }
+
+ public IDevice[] getDevices() {
+ synchronized (deviceMap) {
+ return deviceList.toArray(new IDevice[deviceList.size()]);
+ }
+ }
+
+ public Window[] getWindows(IDevice device) {
+ Window[] windows;
+ synchronized (deviceMap) {
+ windows = deviceMap.get(device);
+ }
+ return windows;
+ }
+
+ // Returns the window that currently has focus or -1. Note that this means
+ // that a window with hashcode -1 gets highlighted. If you remember, this is
+ // the infamous <Focused Window>
+ public int getFocusedWindow(IDevice device) {
+ synchronized (deviceMap) {
+ Integer focusedWindow = focusedWindowHashes.get(device);
+ if (focusedWindow == null) {
+ return -1;
+ }
+ return focusedWindow.intValue();
+ }
+ }
+
+ public void setSelection(IDevice device, Window window) {
+ synchronized (deviceMap) {
+ selectedDevice = device;
+ selectedWindow = window;
+ }
+ }
+
+ public IDevice getSelectedDevice() {
+ synchronized (deviceMap) {
+ return selectedDevice;
+ }
+ }
+
+ public Window getSelectedWindow() {
+ synchronized (deviceMap) {
+ return selectedWindow;
+ }
+ }
+}