diff options
author | Konstantin Lopyrev <klopyrev@google.com> | 2010-07-26 15:32:06 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2010-07-26 15:32:06 -0700 |
commit | 579d69370b7d99a31ab1aac918bfb76d3c1d4d08 (patch) | |
tree | 14caa7bb8dacf24ce29a193a3f41eb35ac1f32c8 /hierarchyviewer2/libs | |
parent | 63e53f2d074256e335e44ea0c96d652461847f48 (diff) | |
parent | 506d3f2ab9f2ec561a0b3ccd48fa017c24b9cefd (diff) | |
download | sdk-579d69370b7d99a31ab1aac918bfb76d3c1d4d08.zip sdk-579d69370b7d99a31ab1aac918bfb76d3c1d4d08.tar.gz sdk-579d69370b7d99a31ab1aac918bfb76d3c1d4d08.tar.bz2 |
Merge "Device Selector code."
Diffstat (limited to 'hierarchyviewer2/libs')
16 files changed, 1356 insertions, 8 deletions
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 { -} |