1 /*
2  * Copyright (C) 2008 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.hierarchyviewer.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.hierarchyviewer.scene.VersionLoader;
27 
28 import java.io.IOException;
29 import java.io.File;
30 import java.util.HashMap;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 
34 public class DeviceBridge {
35     private static AndroidDebugBridge bridge;
36 
37     private static final HashMap<IDevice, Integer> devicePortMap = new HashMap<IDevice, Integer>();
38     private static int nextLocalPort = Configuration.DEFAULT_SERVER_PORT;
39 
initDebugBridge()40     public static void initDebugBridge() {
41         if (bridge == null) {
42             AndroidDebugBridge.init(false /* debugger support */);
43         }
44         if (bridge == null || !bridge.isConnected()) {
45             String adbLocation = System.getProperty("hierarchyviewer.adb");
46             if (adbLocation != null && adbLocation.length() != 0) {
47                 adbLocation += File.separator + "adb";
48             } else {
49                 adbLocation = "adb";
50             }
51 
52             bridge = AndroidDebugBridge.createBridge(adbLocation, true);
53         }
54     }
55 
startListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener)56     public static void startListenForDevices(AndroidDebugBridge.IDeviceChangeListener listener) {
57         AndroidDebugBridge.addDeviceChangeListener(listener);
58     }
59 
getDevices()60     public static IDevice[] getDevices() {
61         return bridge.getDevices();
62     }
63 
isViewServerRunning(IDevice device)64     public static boolean isViewServerRunning(IDevice device) {
65         initDebugBridge();
66         final boolean[] result = new boolean[1];
67         try {
68             if (device.isOnline()) {
69                 device.executeShellCommand(buildIsServerRunningShellCommand(),
70                         new BooleanResultReader(result));
71                 if (!result[0]) {
72                     if (VersionLoader.loadProtocolVersion(device) > 2) {
73                         result[0] = true;
74                     }
75                 }
76             }
77         } catch (IOException e) {
78             e.printStackTrace();
79         } catch (TimeoutException e) {
80             e.printStackTrace();
81         } catch (AdbCommandRejectedException e) {
82             e.printStackTrace();
83         } catch (ShellCommandUnresponsiveException e) {
84             e.printStackTrace();
85         }
86         return result[0];
87     }
88 
startViewServer(IDevice device)89     public static boolean startViewServer(IDevice device) {
90         return startViewServer(device, Configuration.DEFAULT_SERVER_PORT);
91     }
92 
startViewServer(IDevice device, int port)93     public static boolean startViewServer(IDevice device, int port) {
94         initDebugBridge();
95         final boolean[] result = new boolean[1];
96         try {
97             if (device.isOnline()) {
98                 device.executeShellCommand(buildStartServerShellCommand(port),
99                         new BooleanResultReader(result));
100             }
101         } catch (IOException e) {
102             e.printStackTrace();
103         } catch (TimeoutException e) {
104             e.printStackTrace();
105         } catch (AdbCommandRejectedException e) {
106             e.printStackTrace();
107         } catch (ShellCommandUnresponsiveException e) {
108             e.printStackTrace();
109         }
110         return result[0];
111     }
112 
stopViewServer(IDevice device)113     public static boolean stopViewServer(IDevice device) {
114         initDebugBridge();
115         final boolean[] result = new boolean[1];
116         try {
117             if (device.isOnline()) {
118                 device.executeShellCommand(buildStopServerShellCommand(),
119                         new BooleanResultReader(result));
120             }
121         } catch (IOException e) {
122             e.printStackTrace();
123         } catch (TimeoutException e) {
124             e.printStackTrace();
125         } catch (AdbCommandRejectedException e) {
126             e.printStackTrace();
127         } catch (ShellCommandUnresponsiveException e) {
128             e.printStackTrace();
129         }
130         return result[0];
131     }
132 
terminate()133     public static void terminate() {
134         AndroidDebugBridge.terminate();
135     }
136 
137     /**
138      * Sets up a just-connected device to work with the view server.
139      * <p/>This starts a port forwarding between a local port and a port on the device.
140      * @param device
141      */
setupDeviceForward(IDevice device)142     public static void setupDeviceForward(IDevice device) {
143         synchronized (devicePortMap) {
144             if (device.getState() == IDevice.DeviceState.ONLINE) {
145                 int localPort = nextLocalPort++;
146                 try {
147                     device.createForward(localPort, Configuration.DEFAULT_SERVER_PORT);
148                     devicePortMap.put(device, localPort);
149                 } catch (TimeoutException e) {
150                     Log.e("hierarchy", "Timeout setting up port forwarding for " + device);
151                 } catch (AdbCommandRejectedException e) {
152                     Log.e("hierarchy", String.format(
153                             "Adb rejected forward command for device %1$s: %2$s",
154                             device, e.getMessage()));
155                 } catch (IOException e) {
156                     Log.e("hierarchy", String.format(
157                             "Failed to create forward for device %1$s: %2$s",
158                             device, e.getMessage()));
159                 }
160             }
161         }
162     }
163 
removeDeviceForward(IDevice device)164     public static void removeDeviceForward(IDevice device) {
165         synchronized (devicePortMap) {
166             final Integer localPort = devicePortMap.get(device);
167             if (localPort != null) {
168                 try {
169                     device.removeForward(localPort, Configuration.DEFAULT_SERVER_PORT);
170                     devicePortMap.remove(device);
171                 } catch (TimeoutException e) {
172                     Log.e("hierarchy", "Timeout removing port forwarding for " + device);
173                 } catch (AdbCommandRejectedException e) {
174                     Log.e("hierarchy", String.format(
175                             "Adb rejected remove-forward command for device %1$s: %2$s",
176                             device, e.getMessage()));
177                 } catch (IOException e) {
178                     Log.e("hierarchy", String.format(
179                             "Failed to remove forward for device %1$s: %2$s",
180                             device, e.getMessage()));
181                 }
182             }
183         }
184     }
185 
getDeviceLocalPort(IDevice device)186     public static int getDeviceLocalPort(IDevice device) {
187         synchronized (devicePortMap) {
188             Integer port = devicePortMap.get(device);
189             if (port != null) {
190                 return port;
191             }
192 
193             Log.e("hierarchy", "Missing forwarded port for " + device.getSerialNumber());
194             return -1;
195         }
196 
197     }
198 
buildStartServerShellCommand(int port)199     private static String buildStartServerShellCommand(int port) {
200         return String.format("service call window %d i32 %d",
201                 Configuration.SERVICE_CODE_START_SERVER, port);
202     }
203 
buildStopServerShellCommand()204     private static String buildStopServerShellCommand() {
205         return String.format("service call window %d", Configuration.SERVICE_CODE_STOP_SERVER);
206     }
207 
buildIsServerRunningShellCommand()208     private static String buildIsServerRunningShellCommand() {
209         return String.format("service call window %d",
210                 Configuration.SERVICE_CODE_IS_SERVER_RUNNING);
211     }
212 
213     private static class BooleanResultReader extends MultiLineReceiver {
214         private final boolean[] mResult;
215 
BooleanResultReader(boolean[] result)216         public BooleanResultReader(boolean[] result) {
217             mResult = result;
218         }
219 
220         @Override
processNewLines(String[] strings)221         public void processNewLines(String[] strings) {
222             if (strings.length > 0) {
223                 Pattern pattern = Pattern.compile(".*?\\([0-9]{8} ([0-9]{8}).*");
224                 Matcher matcher = pattern.matcher(strings[0]);
225                 if (matcher.matches()) {
226                     if (Integer.parseInt(matcher.group(1)) == 1) {
227                         mResult[0] = true;
228                     }
229                 }
230             }
231         }
232 
isCancelled()233         public boolean isCancelled() {
234             return false;
235         }
236     }
237 }
238