OSDN Git Service

Device Selector code.
authorKonstantin Lopyrev <klopyrev@google.com>
Thu, 22 Jul 2010 21:23:47 +0000 (14:23 -0700)
committerKonstantin Lopyrev <klopyrev@google.com>
Fri, 23 Jul 2010 22:28:02 +0000 (15:28 -0700)
Change-Id: I944315e75b72ef285bed53e5801761886f48cb24

23 files changed:
eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/.gitignore [new file with mode: 0644]
hierarchyviewer2/app/.classpath
hierarchyviewer2/app/.gitignore [new file with mode: 0644]
hierarchyviewer2/app/src/Android.mk
hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java
hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplicationDirector.java [new file with mode: 0644]
hierarchyviewer2/app/src/com/android/hierarchyviewer/UIThread.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/.classpath
hierarchyviewer2/libs/hierarchyviewerlib/.gitignore [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/Test.java [deleted file]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/Window.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/WindowUpdater.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyvieweruilib/.classpath
hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk
hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java [new file with mode: 0644]
hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/Test.java [deleted file]

diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/.gitignore b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/.gitignore
new file mode 100644 (file)
index 0000000..2fd4c3b
--- /dev/null
@@ -0,0 +1 @@
+libs/
index fb50116..f7a8bdf 100644 (file)
@@ -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 (file)
index 0000000..e660fd9
--- /dev/null
@@ -0,0 +1 @@
+bin/
index dedc028..d8d8fc4 100644 (file)
@@ -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
 
index acc79b7..427add4 100644 (file)
 
 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 (file)
index 0000000..5321ce7
--- /dev/null
@@ -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 (file)
index 0000000..f59f6c4
--- /dev/null
@@ -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();
+    }
+}
index fb50116..b0326c8 100644 (file)
@@ -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 (file)
index 0000000..e660fd9
--- /dev/null
@@ -0,0 +1 @@
+bin/
index 77808b1..1be1a29 100644 (file)
@@ -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 (file)
index 0000000..0f463d9
--- /dev/null
@@ -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 (file)
index 0000000..e60a6f2
--- /dev/null
@@ -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 (file)
index ad6db3d..0000000
+++ /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 (file)
index 0000000..4edf67f
--- /dev/null
@@ -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 (file)
index 0000000..581f76b
--- /dev/null
@@ -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 (file)
index 0000000..1869a51
--- /dev/null
@@ -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 (file)
index 0000000..26797d2
--- /dev/null
@@ -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 (file)
index 0000000..7c4f2f6
--- /dev/null
@@ -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;
+        }
+    }
+}
index fb50116..4bddd6c 100644 (file)
@@ -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 (file)
index 0000000..e660fd9
--- /dev/null
@@ -0,0 +1 @@
+bin/
index 0240688..44d6b51 100644 (file)
@@ -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 (file)
index 0000000..a948d12
--- /dev/null
@@ -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 (file)
index c519cbc..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.android.hierarchyvieweruilib;
-public class Test {
-}