aboutsummaryrefslogtreecommitdiffstats
path: root/hierarchyviewer2
diff options
context:
space:
mode:
authorKonstantin Lopyrev <klopyrev@google.com>2010-07-26 15:32:06 -0700
committerAndroid Code Review <code-review@android.com>2010-07-26 15:32:06 -0700
commit579d69370b7d99a31ab1aac918bfb76d3c1d4d08 (patch)
tree14caa7bb8dacf24ce29a193a3f41eb35ac1f32c8 /hierarchyviewer2
parent63e53f2d074256e335e44ea0c96d652461847f48 (diff)
parent506d3f2ab9f2ec561a0b3ccd48fa017c24b9cefd (diff)
downloadsdk-579d69370b7d99a31ab1aac918bfb76d3c1d4d08.zip
sdk-579d69370b7d99a31ab1aac918bfb76d3c1d4d08.tar.gz
sdk-579d69370b7d99a31ab1aac918bfb76d3c1d4d08.tar.bz2
Merge "Device Selector code."
Diffstat (limited to 'hierarchyviewer2')
-rw-r--r--hierarchyviewer2/app/.classpath6
-rw-r--r--hierarchyviewer2/app/.gitignore1
-rw-r--r--hierarchyviewer2/app/src/Android.mk10
-rw-r--r--hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java17
-rw-r--r--hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplicationDirector.java66
-rw-r--r--hierarchyviewer2/app/src/com/android/hierarchyviewer/UIThread.java42
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/.classpath1
-rw-r--r--hierarchyviewer2/libs/hierarchyviewerlib/.gitignore1
-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
-rw-r--r--hierarchyviewer2/libs/hierarchyvieweruilib/.classpath4
-rw-r--r--hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore1
-rw-r--r--hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk7
-rw-r--r--hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java257
-rw-r--r--hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/Test.java3
22 files changed, 1493 insertions, 13 deletions
diff --git a/hierarchyviewer2/app/.classpath b/hierarchyviewer2/app/.classpath
index fb50116..f7a8bdf 100644
--- a/hierarchyviewer2/app/.classpath
+++ b/hierarchyviewer2/app/.classpath
@@ -2,5 +2,11 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/hierarchyviewerlib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/hierarchyvieweruilib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/hierarchyviewer2/app/.gitignore b/hierarchyviewer2/app/.gitignore
new file mode 100644
index 0000000..e660fd9
--- /dev/null
+++ b/hierarchyviewer2/app/.gitignore
@@ -0,0 +1 @@
+bin/
diff --git a/hierarchyviewer2/app/src/Android.mk b/hierarchyviewer2/app/src/Android.mk
index dedc028..d8d8fc4 100644
--- a/hierarchyviewer2/app/src/Android.mk
+++ b/hierarchyviewer2/app/src/Android.mk
@@ -19,10 +19,12 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAR_MANIFEST := ../etc/manifest.txt
LOCAL_JAVA_LIBRARIES := \
- ddmlib \
- hierarchyviewerlib \
- hierarchyvieweruilib \
- swt
+ ddmlib \
+ ddmuilib \
+ hierarchyviewerlib \
+ hierarchyvieweruilib \
+ swt \
+ sdklib
LOCAL_MODULE := hierarchyviewer2
diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java
index acc79b7..427add4 100644
--- a/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java
+++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java
@@ -16,8 +16,23 @@
package com.android.hierarchyviewer;
+import com.android.hierarchyviewerlib.ComponentRegistry;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
+
public class HierarchyViewerApplication {
public static void main(String[] args) {
- System.out.println("TEST");
+ HierarchyViewerDirector director = new HierarchyViewerApplicationDirector();
+ ComponentRegistry.setDirector(director);
+ director.initDebugBridge();
+ ComponentRegistry.setDeviceSelectionModel(new DeviceSelectionModel());
+ director.startListenForDevices();
+ director.populateDeviceSelectionModel();
+
+ UIThread.runUI();
+
+ director.stopListenForDevices();
+ director.stopDebugBridge();
+ director.terminate();
}
}
diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplicationDirector.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplicationDirector.java
new file mode 100644
index 0000000..5321ce7
--- /dev/null
+++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplicationDirector.java
@@ -0,0 +1,66 @@
+/*
+ * 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.hierarchyviewer;
+
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+import com.android.sdklib.SdkConstants;
+
+import java.io.File;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * This is the application version of the director.
+ */
+public class HierarchyViewerApplicationDirector extends HierarchyViewerDirector {
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ @Override
+ public void terminate() {
+ super.terminate();
+ executor.shutdown();
+ }
+
+ /*
+ * Gets the location of adb. The script that runs the hierarchy viewer
+ * defines com.android.hierarchyviewer.bindir.
+ */
+ @Override
+ public String getAdbLocation() {
+ String hvParentLocation = System.getProperty("com.android.hierarchyviewer.bindir");
+ if (hvParentLocation != null && hvParentLocation.length() != 0) {
+ return hvParentLocation + File.separator + SdkConstants.FN_ADB;
+ }
+ return SdkConstants.FN_ADB;
+ }
+
+ /*
+ * In the application, we handle background tasks using a single thread,
+ * just to get rid of possible race conditions that can occur. We update the
+ * progress bar to show that we are doing something in the background.
+ */
+ @Override
+ public void executeInBackground(final Runnable task) {
+ executor.execute(new Runnable() {
+ public void run() {
+ task.run();
+ }
+ });
+ }
+
+}
diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/UIThread.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/UIThread.java
new file mode 100644
index 0000000..f59f6c4
--- /dev/null
+++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/UIThread.java
@@ -0,0 +1,42 @@
+/*
+ * 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.hierarchyviewer;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyvieweruilib.DeviceSelector;
+
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class UIThread {
+ public static void runUI() {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setLayout(new FillLayout());
+ DeviceSelector deviceSelector = new DeviceSelector(shell);
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ deviceSelector.terminate();
+ ImageLoader.dispose();
+ display.dispose();
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/.classpath b/hierarchyviewer2/libs/hierarchyviewerlib/.classpath
index fb50116..b0326c8 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/.classpath
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/.classpath
@@ -2,5 +2,6 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/.gitignore b/hierarchyviewer2/libs/hierarchyviewerlib/.gitignore
new file mode 100644
index 0000000..e660fd9
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/.gitignore
@@ -0,0 +1 @@
+bin/
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;
+ }
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath b/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath
index fb50116..4bddd6c 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath
+++ b/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath
@@ -2,5 +2,9 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/hierarchyviewerlib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore b/hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore
new file mode 100644
index 0000000..e660fd9
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore
@@ -0,0 +1 @@
+bin/
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk b/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk
index 0240688..44d6b51 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk
+++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk
@@ -18,8 +18,11 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := \
- hierarchyviewerlib \
- swt
+ ddmlib \
+ ddmuilib \
+ hierarchyviewerlib \
+ swt \
+ org.eclipse.jface_3.4.2.M20090107-0800
LOCAL_MODULE := hierarchyvieweruilib
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java
new file mode 100644
index 0000000..a948d12
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java
@@ -0,0 +1,257 @@
+/*
+ * 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.hierarchyvieweruilib;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.ComponentRegistry;
+import com.android.hierarchyviewerlib.device.Window;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel.WindowChangeListener;
+
+import org.eclipse.jface.viewers.IFontProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class DeviceSelector implements WindowChangeListener, SelectionListener {
+ private TreeViewer treeViewer;
+
+ private Tree tree;
+
+ private DeviceSelectionModel model;
+
+ private Font boldFont;
+
+ private Image deviceImage;
+
+ private Image emulatorImage;
+
+ private final static int ICON_WIDTH = 16;
+
+ private class ContentProvider implements ITreeContentProvider, ILabelProvider, IFontProvider {
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof IDevice) {
+ Window[] list = model.getWindows((IDevice) parentElement);
+ if (list != null) {
+ return list;
+ }
+ }
+ return new Object[0];
+ }
+
+ public Object getParent(Object element) {
+ if (element instanceof Window) {
+ return ((Window) element).getDevice();
+ }
+ return null;
+ }
+
+ public boolean hasChildren(Object element) {
+ if (element instanceof IDevice) {
+ Window[] list = model.getWindows((IDevice) element);
+ if (list != null) {
+ return list.length != 0;
+ }
+ }
+ return false;
+ }
+
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof DeviceSelectionModel) {
+ return model.getDevices();
+ }
+ return new Object[0];
+ }
+
+ public void dispose() {
+ // pass
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ // pass
+ }
+
+ public Image getImage(Object element) {
+ if (element instanceof IDevice) {
+ if (((IDevice) element).isEmulator()) {
+ return emulatorImage;
+ }
+ return deviceImage;
+ }
+ return null;
+ }
+
+ public String getText(Object element) {
+ if (element instanceof IDevice) {
+ return ((IDevice) element).toString();
+ } else if (element instanceof Window) {
+ return ((Window) element).getTitle();
+ }
+ return null;
+ }
+
+ public Font getFont(Object element) {
+ if (element instanceof Window) {
+ int focusedWindow = model.getFocusedWindow(((Window) element).getDevice());
+ if (focusedWindow == ((Window) element).getHashCode()) {
+ return boldFont;
+ }
+ }
+ return null;
+ }
+
+ public void addListener(ILabelProviderListener listener) {
+ // pass
+ }
+
+ public boolean isLabelProperty(Object element, String property) {
+ // pass
+ return false;
+ }
+
+ public void removeListener(ILabelProviderListener listener) {
+ // pass
+ }
+ }
+
+ public DeviceSelector(Composite parent) {
+ treeViewer = new TreeViewer(parent, SWT.SINGLE);
+ treeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
+
+ tree = treeViewer.getTree();
+ TreeColumn col = new TreeColumn(tree, SWT.LEFT);
+ col.setText("Name");
+ col.pack();
+ tree.setHeaderVisible(true);
+ tree.setLinesVisible(true);
+ tree.addSelectionListener(this);
+
+ loadResources();
+
+ model = ComponentRegistry.getDeviceSelectionModel();
+ ContentProvider contentProvider = new ContentProvider();
+ treeViewer.setContentProvider(contentProvider);
+ treeViewer.setLabelProvider(contentProvider);
+ treeViewer.setInput(model);
+ model.addWindowChangeListener(this);
+
+ }
+
+ public void loadResources() {
+ Display display = Display.getDefault();
+ Font systemFont = display.getSystemFont();
+ FontData[] fontData = systemFont.getFontData();
+ FontData[] newFontData = new FontData[fontData.length];
+ for (int i = 0; i < fontData.length; i++) {
+ newFontData[i] =
+ new FontData(fontData[i].getName(), fontData[i].getHeight(), fontData[i]
+ .getStyle()
+ | SWT.BOLD);
+ }
+ boldFont = new Font(Display.getDefault(), newFontData);
+
+ ImageLoader loader = ImageLoader.getDdmUiLibLoader();
+ deviceImage =
+ loader.loadImage(display, "device.png", ICON_WIDTH, ICON_WIDTH, display
+ .getSystemColor(SWT.COLOR_RED));
+
+ emulatorImage =
+ loader.loadImage(display, "emulator.png", ICON_WIDTH, ICON_WIDTH, display
+ .getSystemColor(SWT.COLOR_BLUE));
+ }
+
+ public void terminate() {
+ model.removeWindowChangeListener(this);
+ boldFont.dispose();
+ }
+
+ public void setFocus() {
+ tree.setFocus();
+ }
+
+ public void deviceConnected(final IDevice device) {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ treeViewer.refresh();
+ treeViewer.setExpandedState(device, true);
+ }
+ });
+ }
+
+ public void deviceChanged(final IDevice device) {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ TreeSelection selection = (TreeSelection) treeViewer.getSelection();
+ treeViewer.refresh(device);
+ if (selection.getFirstElement() instanceof Window
+ && ((Window) selection.getFirstElement()).getDevice() == device) {
+ treeViewer.setSelection(selection, true);
+ }
+ }
+ });
+ }
+
+ public void deviceDisconnected(final IDevice device) {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ treeViewer.refresh();
+ }
+ });
+ }
+
+ public void focusChanged(final IDevice device) {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ TreeSelection selection = (TreeSelection) treeViewer.getSelection();
+ treeViewer.refresh(device);
+ if (selection.getFirstElement() instanceof Window
+ && ((Window) selection.getFirstElement()).getDevice() == device) {
+ treeViewer.setSelection(selection, true);
+ }
+ }
+ });
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ // TODO: Double click to open view hierarchy
+
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ Object selection = ((TreeItem) e.item).getData();
+ if (selection instanceof IDevice) {
+ model.setSelection((IDevice) selection, null);
+ } else if (selection instanceof Window) {
+ model.setSelection(((Window) selection).getDevice(), (Window) selection);
+ }
+ }
+}
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/Test.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/Test.java
deleted file mode 100644
index c519cbc..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/Test.java
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.android.hierarchyvieweruilib;
-public class Test {
-}