• 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 
291         @Override
isCancelled()292         public boolean isCancelled() {
293             return false;
294         }
295     }
296 
loadViewServerInfo(IDevice device)297     public static ViewServerInfo loadViewServerInfo(IDevice device) {
298         int server = -1;
299         int protocol = -1;
300         DeviceConnection connection = null;
301         try {
302             connection = new DeviceConnection(device);
303             connection.sendCommand("SERVER"); //$NON-NLS-1$
304             String line = connection.getInputStream().readLine();
305             if (line != null) {
306                 server = Integer.parseInt(line);
307             }
308         } catch (Exception e) {
309             Log.e(TAG, "Unable to get view server version from device " + device);
310         } finally {
311             if (connection != null) {
312                 connection.close();
313             }
314         }
315         connection = null;
316         try {
317             connection = new DeviceConnection(device);
318             connection.sendCommand("PROTOCOL"); //$NON-NLS-1$
319             String line = connection.getInputStream().readLine();
320             if (line != null) {
321                 protocol = Integer.parseInt(line);
322             }
323         } catch (Exception e) {
324             Log.e(TAG, "Unable to get view server protocol version from device " + device);
325         } finally {
326             if (connection != null) {
327                 connection.close();
328             }
329         }
330         if (server == -1 || protocol == -1) {
331             return null;
332         }
333         ViewServerInfo returnValue = new ViewServerInfo(server, protocol);
334         synchronized (sViewServerInfo) {
335             sViewServerInfo.put(device, returnValue);
336         }
337         return returnValue;
338     }
339 
getViewServerInfo(IDevice device)340     public static ViewServerInfo getViewServerInfo(IDevice device) {
341         synchronized (sViewServerInfo) {
342             return sViewServerInfo.get(device);
343         }
344     }
345 
removeViewServerInfo(IDevice device)346     public static void removeViewServerInfo(IDevice device) {
347         synchronized (sViewServerInfo) {
348             sViewServerInfo.remove(device);
349         }
350     }
351 
352     /*
353      * This loads the list of windows from the specified device. The format is:
354      * hashCode1 title1 hashCode2 title2 ... hashCodeN titleN DONE.
355      */
loadWindows(IDevice device)356     public static Window[] loadWindows(IDevice device) {
357         ArrayList<Window> windows = new ArrayList<Window>();
358         DeviceConnection connection = null;
359         ViewServerInfo serverInfo = getViewServerInfo(device);
360         try {
361             connection = new DeviceConnection(device);
362             connection.sendCommand("LIST"); //$NON-NLS-1$
363             BufferedReader in = connection.getInputStream();
364             String line;
365             while ((line = in.readLine()) != null) {
366                 if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
367                     break;
368                 }
369 
370                 int index = line.indexOf(' ');
371                 if (index != -1) {
372                     String windowId = line.substring(0, index);
373 
374                     int id;
375                     if (serverInfo.serverVersion > 2) {
376                         id = (int) Long.parseLong(windowId, 16);
377                     } else {
378                         id = Integer.parseInt(windowId, 16);
379                     }
380 
381                     Window w = new Window(device, line.substring(index + 1), id);
382                     windows.add(w);
383                 }
384             }
385             // Automatic refreshing of windows was added in protocol version 3.
386             // Before, the user needed to specify explicitly that he wants to
387             // get the focused window, which was done using a special type of
388             // window with hash code -1.
389             if (serverInfo.protocolVersion < 3) {
390                 windows.add(Window.getFocusedWindow(device));
391             }
392         } catch (Exception e) {
393             Log.e(TAG, "Unable to load the window list from device " + device);
394         } finally {
395             if (connection != null) {
396                 connection.close();
397             }
398         }
399         // The server returns the list of windows from the window at the bottom
400         // to the top. We want the reverse order to put the top window on top of
401         // the list.
402         Window[] returnValue = new Window[windows.size()];
403         for (int i = windows.size() - 1; i >= 0; i--) {
404             returnValue[returnValue.length - i - 1] = windows.get(i);
405         }
406         return returnValue;
407     }
408 
409     /*
410      * This gets the hash code of the window that has focus. Only works with
411      * protocol version 3 and above.
412      */
getFocusedWindow(IDevice device)413     public static int getFocusedWindow(IDevice device) {
414         DeviceConnection connection = null;
415         try {
416             connection = new DeviceConnection(device);
417             connection.sendCommand("GET_FOCUS"); //$NON-NLS-1$
418             String line = connection.getInputStream().readLine();
419             if (line == null || line.length() == 0) {
420                 return -1;
421             }
422             return (int) Long.parseLong(line.substring(0, line.indexOf(' ')), 16);
423         } catch (Exception e) {
424             Log.e(TAG, "Unable to get the focused window from device " + device);
425         } finally {
426             if (connection != null) {
427                 connection.close();
428             }
429         }
430         return -1;
431     }
432 
loadWindowData(Window window)433     public static ViewNode loadWindowData(Window window) {
434         DeviceConnection connection = null;
435         try {
436             connection = new DeviceConnection(window.getDevice());
437             connection.sendCommand("DUMP " + window.encode()); //$NON-NLS-1$
438             BufferedReader in = connection.getInputStream();
439             ViewNode currentNode = null;
440             int currentDepth = -1;
441             String line;
442             while ((line = in.readLine()) != null) {
443                 if ("DONE.".equalsIgnoreCase(line)) {
444                     break;
445                 }
446                 int depth = 0;
447                 while (line.charAt(depth) == ' ') {
448                     depth++;
449                 }
450                 while (depth <= currentDepth) {
451                     currentNode = currentNode.parent;
452                     currentDepth--;
453                 }
454                 currentNode = new ViewNode(window, currentNode, line.substring(depth));
455                 currentDepth = depth;
456             }
457             if (currentNode == null) {
458                 return null;
459             }
460             while (currentNode.parent != null) {
461                 currentNode = currentNode.parent;
462             }
463             ViewServerInfo serverInfo = getViewServerInfo(window.getDevice());
464             if (serverInfo != null) {
465                 currentNode.protocolVersion = serverInfo.protocolVersion;
466             }
467             return currentNode;
468         } catch (Exception e) {
469             Log.e(TAG, "Unable to load window data for window " + window.getTitle() + " on device "
470                     + window.getDevice());
471         } finally {
472             if (connection != null) {
473                 connection.close();
474             }
475         }
476         return null;
477     }
478 
loadProfileData(Window window, ViewNode viewNode)479     public static boolean loadProfileData(Window window, ViewNode viewNode) {
480         DeviceConnection connection = null;
481         try {
482             connection = new DeviceConnection(window.getDevice());
483             connection.sendCommand("PROFILE " + window.encode() + " " + viewNode.toString()); //$NON-NLS-1$
484             BufferedReader in = connection.getInputStream();
485             int protocol;
486             synchronized (sViewServerInfo) {
487                 protocol = sViewServerInfo.get(window.getDevice()).protocolVersion;
488             }
489             if (protocol < 3) {
490                 return loadProfileData(viewNode, in);
491             } else {
492                 boolean ret = loadProfileDataRecursive(viewNode, in);
493                 if (ret) {
494                     viewNode.setProfileRatings();
495                 }
496                 return ret;
497             }
498         } catch (Exception e) {
499             Log.e(TAG, "Unable to load profiling data for window " + window.getTitle()
500                     + " on device " + window.getDevice());
501         } finally {
502             if (connection != null) {
503                 connection.close();
504             }
505         }
506         return false;
507     }
508 
loadProfileData(ViewNode node, BufferedReader in)509     private static boolean loadProfileData(ViewNode node, BufferedReader in) throws IOException {
510         String line;
511         if ((line = in.readLine()) == null || line.equalsIgnoreCase("-1 -1 -1") //$NON-NLS-1$
512                 || line.equalsIgnoreCase("DONE.")) { //$NON-NLS-1$
513             return false;
514         }
515         String[] data = line.split(" ");
516         node.measureTime = (Long.parseLong(data[0]) / 1000.0) / 1000.0;
517         node.layoutTime = (Long.parseLong(data[1]) / 1000.0) / 1000.0;
518         node.drawTime = (Long.parseLong(data[2]) / 1000.0) / 1000.0;
519         return true;
520     }
521 
loadProfileDataRecursive(ViewNode node, BufferedReader in)522     private static boolean loadProfileDataRecursive(ViewNode node, BufferedReader in)
523             throws IOException {
524         if (!loadProfileData(node, in)) {
525             return false;
526         }
527         for (int i = 0; i < node.children.size(); i++) {
528             if (!loadProfileDataRecursive(node.children.get(i), in)) {
529                 return false;
530             }
531         }
532         return true;
533     }
534 
loadCapture(Window window, ViewNode viewNode)535     public static Image loadCapture(Window window, ViewNode viewNode) {
536         DeviceConnection connection = null;
537         try {
538             connection = new DeviceConnection(window.getDevice());
539             connection.getSocket().setSoTimeout(5000);
540             connection.sendCommand("CAPTURE " + window.encode() + " " + viewNode.toString()); //$NON-NLS-1$
541             return new Image(Display.getDefault(), connection.getSocket().getInputStream());
542         } catch (Exception e) {
543             Log.e(TAG, "Unable to capture data for node " + viewNode + " in window "
544                     + window.getTitle() + " on device " + window.getDevice());
545         } finally {
546             if (connection != null) {
547                 connection.close();
548             }
549         }
550         return null;
551     }
552 
captureLayers(Window window)553     public static PsdFile captureLayers(Window window) {
554         DeviceConnection connection = null;
555         DataInputStream in = null;
556 
557         try {
558             connection = new DeviceConnection(window.getDevice());
559 
560             connection.sendCommand("CAPTURE_LAYERS " + window.encode()); //$NON-NLS-1$
561 
562             in =
563                     new DataInputStream(new BufferedInputStream(connection.getSocket()
564                             .getInputStream()));
565 
566             int width = in.readInt();
567             int height = in.readInt();
568 
569             PsdFile psd = new PsdFile(width, height);
570 
571             while (readLayer(in, psd)) {
572             }
573 
574             return psd;
575         } catch (Exception e) {
576             Log.e(TAG, "Unable to capture layers for window " + window.getTitle() + " on device "
577                     + window.getDevice());
578         } finally {
579             if (in != null) {
580                 try {
581                     in.close();
582                 } catch (Exception ex) {
583                 }
584             }
585             connection.close();
586         }
587 
588         return null;
589     }
590 
readLayer(DataInputStream in, PsdFile psd)591     private static boolean readLayer(DataInputStream in, PsdFile psd) {
592         try {
593             if (in.read() == 2) {
594                 return false;
595             }
596             String name = in.readUTF();
597             boolean visible = in.read() == 1;
598             int x = in.readInt();
599             int y = in.readInt();
600             int dataSize = in.readInt();
601 
602             byte[] data = new byte[dataSize];
603             int read = 0;
604             while (read < dataSize) {
605                 read += in.read(data, read, dataSize - read);
606             }
607 
608             ByteArrayInputStream arrayIn = new ByteArrayInputStream(data);
609             BufferedImage chunk = ImageIO.read(arrayIn);
610 
611             // Ensure the image is in the right format
612             BufferedImage image =
613                     new BufferedImage(chunk.getWidth(), chunk.getHeight(),
614                             BufferedImage.TYPE_INT_ARGB);
615             Graphics2D g = image.createGraphics();
616             g.drawImage(chunk, null, 0, 0);
617             g.dispose();
618 
619             psd.addLayer(name, image, new Point(x, y), visible);
620 
621             return true;
622         } catch (Exception e) {
623             return false;
624         }
625     }
626 
invalidateView(ViewNode viewNode)627     public static void invalidateView(ViewNode viewNode) {
628         DeviceConnection connection = null;
629         try {
630             connection = new DeviceConnection(viewNode.window.getDevice());
631             connection.sendCommand("INVALIDATE " + viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$
632         } catch (Exception e) {
633             Log.e(TAG, "Unable to invalidate view " + viewNode + " in window " + viewNode.window
634                     + " on device " + viewNode.window.getDevice());
635         } finally {
636             connection.close();
637         }
638     }
639 
requestLayout(ViewNode viewNode)640     public static void requestLayout(ViewNode viewNode) {
641         DeviceConnection connection = null;
642         try {
643             connection = new DeviceConnection(viewNode.window.getDevice());
644             connection.sendCommand("REQUEST_LAYOUT " + viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$
645         } catch (Exception e) {
646             Log.e(TAG, "Unable to request layout for node " + viewNode + " in window "
647                     + viewNode.window + " on device " + viewNode.window.getDevice());
648         } finally {
649             connection.close();
650         }
651     }
652 
outputDisplayList(ViewNode viewNode)653     public static void outputDisplayList(ViewNode viewNode) {
654         DeviceConnection connection = null;
655         try {
656             connection = new DeviceConnection(viewNode.window.getDevice());
657             connection.sendCommand("OUTPUT_DISPLAYLIST " +
658                     viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$
659         } catch (Exception e) {
660             Log.e(TAG, "Unable to dump displaylist for node " + viewNode + " in window "
661                     + viewNode.window + " on device " + viewNode.window.getDevice());
662         } finally {
663             connection.close();
664         }
665     }
666 
667 }
668