• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.hierarchyviewerlib.device;
18 
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.AndroidDebugBridge;
21 import com.android.ddmlib.IDevice;
22 import com.android.ddmlib.Log;
23 import com.android.ddmlib.MultiLineReceiver;
24 import com.android.ddmlib.ShellCommandUnresponsiveException;
25 import com.android.ddmlib.TimeoutException;
26 import com.android.hierarchyviewerlib.ui.util.PsdFile;
27 
28 import org.eclipse.swt.graphics.Image;
29 import org.eclipse.swt.widgets.Display;
30 
31 import java.awt.Graphics2D;
32 import java.awt.Point;
33 import java.awt.image.BufferedImage;
34 import java.io.BufferedInputStream;
35 import java.io.BufferedReader;
36 import java.io.ByteArrayInputStream;
37 import java.io.DataInputStream;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43 
44 import javax.imageio.ImageIO;
45 
46 /**
47  * A bridge to the device.
48  */
49 public class DeviceBridge {
50 
51     public static final String TAG = "hierarchyviewer";
52 
53     private static final int DEFAULT_SERVER_PORT = 4939;
54 
55     // These codes must match the auto-generated codes in IWindowManager.java
56     // See IWindowManager.aidl as well
57     private static final int SERVICE_CODE_START_SERVER = 1;
58 
59     private static final int SERVICE_CODE_STOP_SERVER = 2;
60 
61     private static final int SERVICE_CODE_IS_SERVER_RUNNING = 3;
62 
63     private static AndroidDebugBridge sBridge;
64 
65     private static final HashMap<IDevice, Integer> sDevicePortMap = new HashMap<IDevice, Integer>();
66 
67     private static final HashMap<IDevice, ViewServerInfo> sViewServerInfo =
68             new HashMap<IDevice, ViewServerInfo>();
69 
70     private static int sNextLocalPort = DEFAULT_SERVER_PORT;
71 
72     public static class ViewServerInfo {
73         public final int protocolVersion;
74 
75         public final int serverVersion;
76 
ViewServerInfo(int serverVersion, int protocolVersion)77         ViewServerInfo(int serverVersion, int protocolVersion) {
78             this.protocolVersion = protocolVersion;
79             this.serverVersion = serverVersion;
80         }
81     }
82 
83     /**
84      * Init the DeviceBridge with an existing {@link AndroidDebugBridge}.
85      * @param bridge the bridge object to use
86      */
acquireBridge(AndroidDebugBridge bridge)87     public static void acquireBridge(AndroidDebugBridge bridge) {
88         sBridge = bridge;
89     }
90 
91     /**
92      * Creates an {@link AndroidDebugBridge} connected to adb at the given location.
93      *
94      * If a bridge is already running, this disconnects it and creates a new one.
95      *
96      * @param adbLocation the location to adb.
97      */
initDebugBridge(String adbLocation)98     public static void initDebugBridge(String adbLocation) {
99         if (sBridge == null) {
100             AndroidDebugBridge.init(false /* debugger support */);
101         }
102         if (sBridge == null || !sBridge.isConnected()) {
103             sBridge = AndroidDebugBridge.createBridge(adbLocation, true);
104         }
105     }
106 
107     /** Disconnects the current {@link AndroidDebugBridge}. */
terminate()108     public static void terminate() {
109         AndroidDebugBridge.terminate();
110     }
111 
getDevices()112     public static IDevice[] getDevices() {
113         if (sBridge == null) {
114             return new IDevice[0];
115         }
116         return sBridge.getDevices();
117     }
118 
119     /*
120      * This adds a listener to the debug bridge. The listener is notified of
121      * connecting/disconnecting devices, devices coming online, etc.
122      */
startListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener)123     public static void startListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener) {
124         AndroidDebugBridge.addDeviceChangeListener(listener);
125     }
126 
stopListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener)127     public static void stopListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener) {
128         AndroidDebugBridge.removeDeviceChangeListener(listener);
129     }
130 
131     /**
132      * Sets up a just-connected device to work with the view server.
133      * <p/>
134      * This starts a port forwarding between a local port and a port on the
135      * device.
136      *
137      * @param device
138      */
setupDeviceForward(IDevice device)139     public static void setupDeviceForward(IDevice device) {
140         synchronized (sDevicePortMap) {
141             if (device.getState() == IDevice.DeviceState.ONLINE) {
142                 int localPort = sNextLocalPort++;
143                 try {
144                     device.createForward(localPort, DEFAULT_SERVER_PORT);
145                     sDevicePortMap.put(device, localPort);
146                 } catch (TimeoutException e) {
147                     Log.e(TAG, "Timeout setting up port forwarding for " + device);
148                 } catch (AdbCommandRejectedException e) {
149                     Log.e(TAG, String.format("Adb rejected forward command for device %1$s: %2$s",
150                             device, e.getMessage()));
151                 } catch (IOException e) {
152                     Log.e(TAG, String.format("Failed to create forward for device %1$s: %2$s",
153                             device, e.getMessage()));
154                 }
155             }
156         }
157     }
158 
removeDeviceForward(IDevice device)159     public static void removeDeviceForward(IDevice device) {
160         synchronized (sDevicePortMap) {
161             final Integer localPort = sDevicePortMap.get(device);
162             if (localPort != null) {
163                 try {
164                     device.removeForward(localPort, DEFAULT_SERVER_PORT);
165                     sDevicePortMap.remove(device);
166                 } catch (TimeoutException e) {
167                     Log.e(TAG, "Timeout removing port forwarding for " + device);
168                 } catch (AdbCommandRejectedException e) {
169                     // In this case, we want to fail silently.
170                 } catch (IOException e) {
171                     Log.e(TAG, String.format("Failed to remove forward for device %1$s: %2$s",
172                             device, e.getMessage()));
173                 }
174             }
175         }
176     }
177 
getDeviceLocalPort(IDevice device)178     public static int getDeviceLocalPort(IDevice device) {
179         synchronized (sDevicePortMap) {
180             Integer port = sDevicePortMap.get(device);
181             if (port != null) {
182                 return port;
183             }
184 
185             Log.e(TAG, "Missing forwarded port for " + device.getSerialNumber());
186             return -1;
187         }
188 
189     }
190 
isViewServerRunning(IDevice device)191     public static boolean isViewServerRunning(IDevice device) {
192         final boolean[] result = new boolean[1];
193         try {
194             if (device.isOnline()) {
195                 device.executeShellCommand(buildIsServerRunningShellCommand(),
196                         new BooleanResultReader(result));
197                 if (!result[0]) {
198                     ViewServerInfo serverInfo = loadViewServerInfo(device);
199                     if (serverInfo != null && serverInfo.protocolVersion > 2) {
200                         result[0] = true;
201                     }
202                 }
203             }
204         } catch (TimeoutException e) {
205             Log.e(TAG, "Timeout checking status of view server on device " + device);
206         } catch (IOException e) {
207             Log.e(TAG, "Unable to check status of view server on device " + device);
208         } catch (AdbCommandRejectedException e) {
209             Log.e(TAG, "Adb rejected command to check status of view server on device " + device);
210         } catch (ShellCommandUnresponsiveException e) {
211             Log.e(TAG, "Unable to execute command to check status of view server on device "
212                     + device);
213         }
214         return result[0];
215     }
216 
startViewServer(IDevice device)217     public static boolean startViewServer(IDevice device) {
218         return startViewServer(device, DEFAULT_SERVER_PORT);
219     }
220 
startViewServer(IDevice device, int port)221     public static boolean startViewServer(IDevice device, int port) {
222         final boolean[] result = new boolean[1];
223         try {
224             if (device.isOnline()) {
225                 device.executeShellCommand(buildStartServerShellCommand(port),
226                         new BooleanResultReader(result));
227             }
228         } catch (TimeoutException e) {
229             Log.e(TAG, "Timeout starting view server on device " + device);
230         } catch (IOException e) {
231             Log.e(TAG, "Unable to start view server on device " + device);
232         } catch (AdbCommandRejectedException e) {
233             Log.e(TAG, "Adb rejected command to start view server on device " + device);
234         } catch (ShellCommandUnresponsiveException e) {
235             Log.e(TAG, "Unable to execute command to start view server on device " + device);
236         }
237         return result[0];
238     }
239 
stopViewServer(IDevice device)240     public static boolean stopViewServer(IDevice device) {
241         final boolean[] result = new boolean[1];
242         try {
243             if (device.isOnline()) {
244                 device.executeShellCommand(buildStopServerShellCommand(), new BooleanResultReader(
245                         result));
246             }
247         } catch (TimeoutException e) {
248             Log.e(TAG, "Timeout stopping view server on device " + device);
249         } catch (IOException e) {
250             Log.e(TAG, "Unable to stop view server on device " + device);
251         } catch (AdbCommandRejectedException e) {
252             Log.e(TAG, "Adb rejected command to stop view server on device " + device);
253         } catch (ShellCommandUnresponsiveException e) {
254             Log.e(TAG, "Unable to execute command to stop view server on device " + device);
255         }
256         return result[0];
257     }
258 
buildStartServerShellCommand(int port)259     private static String buildStartServerShellCommand(int port) {
260         return String.format("service call window %d i32 %d", SERVICE_CODE_START_SERVER, port); //$NON-NLS-1$
261     }
262 
buildStopServerShellCommand()263     private static String buildStopServerShellCommand() {
264         return String.format("service call window %d", SERVICE_CODE_STOP_SERVER); //$NON-NLS-1$
265     }
266 
buildIsServerRunningShellCommand()267     private static String buildIsServerRunningShellCommand() {
268         return String.format("service call window %d", SERVICE_CODE_IS_SERVER_RUNNING); //$NON-NLS-1$
269     }
270 
271     private static class BooleanResultReader extends MultiLineReceiver {
272         private final boolean[] mResult;
273 
BooleanResultReader(boolean[] result)274         public BooleanResultReader(boolean[] result) {
275             mResult = result;
276         }
277 
278         @Override
processNewLines(String[] strings)279         public void processNewLines(String[] strings) {
280             if (strings.length > 0) {
281                 Pattern pattern = Pattern.compile(".*?\\([0-9]{8} ([0-9]{8}).*"); //$NON-NLS-1$
282                 Matcher matcher = pattern.matcher(strings[0]);
283                 if (matcher.matches()) {
284                     if (Integer.parseInt(matcher.group(1)) == 1) {
285                         mResult[0] = true;
286                     }
287                 }
288             }
289         }
290 
isCancelled()291         public boolean isCancelled() {
292             return false;
293         }
294     }
295 
loadViewServerInfo(IDevice device)296     public static ViewServerInfo loadViewServerInfo(IDevice device) {
297         int server = -1;
298         int protocol = -1;
299         DeviceConnection connection = null;
300         try {
301             connection = new DeviceConnection(device);
302             connection.sendCommand("SERVER"); //$NON-NLS-1$
303             String line = connection.getInputStream().readLine();
304             if (line != null) {
305                 server = Integer.parseInt(line);
306             }
307         } catch (Exception e) {
308             Log.e(TAG, "Unable to get view server version from device " + device);
309         } finally {
310             if (connection != null) {
311                 connection.close();
312             }
313         }
314         connection = null;
315         try {
316             connection = new DeviceConnection(device);
317             connection.sendCommand("PROTOCOL"); //$NON-NLS-1$
318             String line = connection.getInputStream().readLine();
319             if (line != null) {
320                 protocol = Integer.parseInt(line);
321             }
322         } catch (Exception e) {
323             Log.e(TAG, "Unable to get view server protocol version from device " + device);
324         } finally {
325             if (connection != null) {
326                 connection.close();
327             }
328         }
329         if (server == -1 || protocol == -1) {
330             return null;
331         }
332         ViewServerInfo returnValue = new ViewServerInfo(server, protocol);
333         synchronized (sViewServerInfo) {
334             sViewServerInfo.put(device, returnValue);
335         }
336         return returnValue;
337     }
338 
getViewServerInfo(IDevice device)339     public static ViewServerInfo getViewServerInfo(IDevice device) {
340         synchronized (sViewServerInfo) {
341             return sViewServerInfo.get(device);
342         }
343     }
344 
removeViewServerInfo(IDevice device)345     public static void removeViewServerInfo(IDevice device) {
346         synchronized (sViewServerInfo) {
347             sViewServerInfo.remove(device);
348         }
349     }
350 
351     /*
352      * This loads the list of windows from the specified device. The format is:
353      * hashCode1 title1 hashCode2 title2 ... hashCodeN titleN DONE.
354      */
loadWindows(IDevice device)355     public static Window[] loadWindows(IDevice device) {
356         ArrayList<Window> windows = new ArrayList<Window>();
357         DeviceConnection connection = null;
358         ViewServerInfo serverInfo = getViewServerInfo(device);
359         try {
360             connection = new DeviceConnection(device);
361             connection.sendCommand("LIST"); //$NON-NLS-1$
362             BufferedReader in = connection.getInputStream();
363             String line;
364             while ((line = in.readLine()) != null) {
365                 if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
366                     break;
367                 }
368 
369                 int index = line.indexOf(' ');
370                 if (index != -1) {
371                     String windowId = line.substring(0, index);
372 
373                     int id;
374                     if (serverInfo.serverVersion > 2) {
375                         id = (int) Long.parseLong(windowId, 16);
376                     } else {
377                         id = Integer.parseInt(windowId, 16);
378                     }
379 
380                     Window w = new Window(device, line.substring(index + 1), id);
381                     windows.add(w);
382                 }
383             }
384             // Automatic refreshing of windows was added in protocol version 3.
385             // Before, the user needed to specify explicitly that he wants to
386             // get the focused window, which was done using a special type of
387             // window with hash code -1.
388             if (serverInfo.protocolVersion < 3) {
389                 windows.add(Window.getFocusedWindow(device));
390             }
391         } catch (Exception e) {
392             Log.e(TAG, "Unable to load the window list from device " + device);
393         } finally {
394             if (connection != null) {
395                 connection.close();
396             }
397         }
398         // The server returns the list of windows from the window at the bottom
399         // to the top. We want the reverse order to put the top window on top of
400         // the list.
401         Window[] returnValue = new Window[windows.size()];
402         for (int i = windows.size() - 1; i >= 0; i--) {
403             returnValue[returnValue.length - i - 1] = windows.get(i);
404         }
405         return returnValue;
406     }
407 
408     /*
409      * This gets the hash code of the window that has focus. Only works with
410      * protocol version 3 and above.
411      */
getFocusedWindow(IDevice device)412     public static int getFocusedWindow(IDevice device) {
413         DeviceConnection connection = null;
414         try {
415             connection = new DeviceConnection(device);
416             connection.sendCommand("GET_FOCUS"); //$NON-NLS-1$
417             String line = connection.getInputStream().readLine();
418             if (line == null || line.length() == 0) {
419                 return -1;
420             }
421             return (int) Long.parseLong(line.substring(0, line.indexOf(' ')), 16);
422         } catch (Exception e) {
423             Log.e(TAG, "Unable to get the focused window from device " + device);
424         } finally {
425             if (connection != null) {
426                 connection.close();
427             }
428         }
429         return -1;
430     }
431 
loadWindowData(Window window)432     public static ViewNode loadWindowData(Window window) {
433         DeviceConnection connection = null;
434         try {
435             connection = new DeviceConnection(window.getDevice());
436             connection.sendCommand("DUMP " + window.encode()); //$NON-NLS-1$
437             BufferedReader in = connection.getInputStream();
438             ViewNode currentNode = null;
439             int currentDepth = -1;
440             String line;
441             while ((line = in.readLine()) != null) {
442                 if ("DONE.".equalsIgnoreCase(line)) {
443                     break;
444                 }
445                 int depth = 0;
446                 while (line.charAt(depth) == ' ') {
447                     depth++;
448                 }
449                 while (depth <= currentDepth) {
450                     currentNode = currentNode.parent;
451                     currentDepth--;
452                 }
453                 currentNode = new ViewNode(window, currentNode, line.substring(depth));
454                 currentDepth = depth;
455             }
456             if (currentNode == null) {
457                 return null;
458             }
459             while (currentNode.parent != null) {
460                 currentNode = currentNode.parent;
461             }
462             ViewServerInfo serverInfo = getViewServerInfo(window.getDevice());
463             if (serverInfo != null) {
464                 currentNode.protocolVersion = serverInfo.protocolVersion;
465             }
466             return currentNode;
467         } catch (Exception e) {
468             Log.e(TAG, "Unable to load window data for window " + window.getTitle() + " on device "
469                     + window.getDevice());
470         } finally {
471             if (connection != null) {
472                 connection.close();
473             }
474         }
475         return null;
476     }
477 
loadProfileData(Window window, ViewNode viewNode)478     public static boolean loadProfileData(Window window, ViewNode viewNode) {
479         DeviceConnection connection = null;
480         try {
481             connection = new DeviceConnection(window.getDevice());
482             connection.sendCommand("PROFILE " + window.encode() + " " + viewNode.toString()); //$NON-NLS-1$
483             BufferedReader in = connection.getInputStream();
484             int protocol;
485             synchronized (sViewServerInfo) {
486                 protocol = sViewServerInfo.get(window.getDevice()).protocolVersion;
487             }
488             if (protocol < 3) {
489                 return loadProfileData(viewNode, in);
490             } else {
491                 boolean ret = loadProfileDataRecursive(viewNode, in);
492                 if (ret) {
493                     viewNode.setProfileRatings();
494                 }
495                 return ret;
496             }
497         } catch (Exception e) {
498             Log.e(TAG, "Unable to load profiling data for window " + window.getTitle()
499                     + " on device " + window.getDevice());
500         } finally {
501             if (connection != null) {
502                 connection.close();
503             }
504         }
505         return false;
506     }
507 
loadProfileData(ViewNode node, BufferedReader in)508     private static boolean loadProfileData(ViewNode node, BufferedReader in) throws IOException {
509         String line;
510         if ((line = in.readLine()) == null || line.equalsIgnoreCase("-1 -1 -1") //$NON-NLS-1$
511                 || line.equalsIgnoreCase("DONE.")) { //$NON-NLS-1$
512             return false;
513         }
514         String[] data = line.split(" ");
515         node.measureTime = (Long.parseLong(data[0]) / 1000.0) / 1000.0;
516         node.layoutTime = (Long.parseLong(data[1]) / 1000.0) / 1000.0;
517         node.drawTime = (Long.parseLong(data[2]) / 1000.0) / 1000.0;
518         return true;
519     }
520 
loadProfileDataRecursive(ViewNode node, BufferedReader in)521     private static boolean loadProfileDataRecursive(ViewNode node, BufferedReader in)
522             throws IOException {
523         if (!loadProfileData(node, in)) {
524             return false;
525         }
526         for (int i = 0; i < node.children.size(); i++) {
527             if (!loadProfileDataRecursive(node.children.get(i), in)) {
528                 return false;
529             }
530         }
531         return true;
532     }
533 
loadCapture(Window window, ViewNode viewNode)534     public static Image loadCapture(Window window, ViewNode viewNode) {
535         DeviceConnection connection = null;
536         try {
537             connection = new DeviceConnection(window.getDevice());
538             connection.getSocket().setSoTimeout(5000);
539             connection.sendCommand("CAPTURE " + window.encode() + " " + viewNode.toString()); //$NON-NLS-1$
540             return new Image(Display.getDefault(), connection.getSocket().getInputStream());
541         } catch (Exception e) {
542             Log.e(TAG, "Unable to capture data for node " + viewNode + " in window "
543                     + window.getTitle() + " on device " + window.getDevice());
544         } finally {
545             if (connection != null) {
546                 connection.close();
547             }
548         }
549         return null;
550     }
551 
captureLayers(Window window)552     public static PsdFile captureLayers(Window window) {
553         DeviceConnection connection = null;
554         DataInputStream in = null;
555 
556         try {
557             connection = new DeviceConnection(window.getDevice());
558 
559             connection.sendCommand("CAPTURE_LAYERS " + window.encode()); //$NON-NLS-1$
560 
561             in =
562                     new DataInputStream(new BufferedInputStream(connection.getSocket()
563                             .getInputStream()));
564 
565             int width = in.readInt();
566             int height = in.readInt();
567 
568             PsdFile psd = new PsdFile(width, height);
569 
570             while (readLayer(in, psd)) {
571             }
572 
573             return psd;
574         } catch (Exception e) {
575             Log.e(TAG, "Unable to capture layers for window " + window.getTitle() + " on device "
576                     + window.getDevice());
577         } finally {
578             if (in != null) {
579                 try {
580                     in.close();
581                 } catch (Exception ex) {
582                 }
583             }
584             connection.close();
585         }
586 
587         return null;
588     }
589 
readLayer(DataInputStream in, PsdFile psd)590     private static boolean readLayer(DataInputStream in, PsdFile psd) {
591         try {
592             if (in.read() == 2) {
593                 return false;
594             }
595             String name = in.readUTF();
596             boolean visible = in.read() == 1;
597             int x = in.readInt();
598             int y = in.readInt();
599             int dataSize = in.readInt();
600 
601             byte[] data = new byte[dataSize];
602             int read = 0;
603             while (read < dataSize) {
604                 read += in.read(data, read, dataSize - read);
605             }
606 
607             ByteArrayInputStream arrayIn = new ByteArrayInputStream(data);
608             BufferedImage chunk = ImageIO.read(arrayIn);
609 
610             // Ensure the image is in the right format
611             BufferedImage image =
612                     new BufferedImage(chunk.getWidth(), chunk.getHeight(),
613                             BufferedImage.TYPE_INT_ARGB);
614             Graphics2D g = image.createGraphics();
615             g.drawImage(chunk, null, 0, 0);
616             g.dispose();
617 
618             psd.addLayer(name, image, new Point(x, y), visible);
619 
620             return true;
621         } catch (Exception e) {
622             return false;
623         }
624     }
625 
invalidateView(ViewNode viewNode)626     public static void invalidateView(ViewNode viewNode) {
627         DeviceConnection connection = null;
628         try {
629             connection = new DeviceConnection(viewNode.window.getDevice());
630             connection.sendCommand("INVALIDATE " + viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$
631         } catch (Exception e) {
632             Log.e(TAG, "Unable to invalidate view " + viewNode + " in window " + viewNode.window
633                     + " on device " + viewNode.window.getDevice());
634         } finally {
635             connection.close();
636         }
637     }
638 
requestLayout(ViewNode viewNode)639     public static void requestLayout(ViewNode viewNode) {
640         DeviceConnection connection = null;
641         try {
642             connection = new DeviceConnection(viewNode.window.getDevice());
643             connection.sendCommand("REQUEST_LAYOUT " + viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$
644         } catch (Exception e) {
645             Log.e(TAG, "Unable to request layout for node " + viewNode + " in window "
646                     + viewNode.window + " on device " + viewNode.window.getDevice());
647         } finally {
648             connection.close();
649         }
650     }
651 
outputDisplayList(ViewNode viewNode)652     public static void outputDisplayList(ViewNode viewNode) {
653         DeviceConnection connection = null;
654         try {
655             connection = new DeviceConnection(viewNode.window.getDevice());
656             connection.sendCommand("OUTPUT_DISPLAYLIST " +
657                     viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$
658         } catch (Exception e) {
659             Log.e(TAG, "Unable to dump displaylist for node " + viewNode + " in window "
660                     + viewNode.window + " on device " + viewNode.window.getDevice());
661         } finally {
662             connection.close();
663         }
664     }
665 
666 }
667