1 /*
2  * Copyright (C) 2015 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.preload;
18 
19 import com.android.ddmlib.AndroidDebugBridge;
20 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
21 import com.android.ddmlib.Client;
22 import com.android.ddmlib.IDevice;
23 
24 /**
25  * Helper class for common communication with a Client (the ddms name for a running application).
26  *
27  * Instances take a default timeout parameter that's applied to all functions without explicit
28  * timeout. Timeouts are in milliseconds.
29  */
30 public class ClientUtils {
31 
32     private int defaultTimeout;
33 
ClientUtils()34     public ClientUtils() {
35         this(10000);
36     }
37 
ClientUtils(int defaultTimeout)38     public ClientUtils(int defaultTimeout) {
39         this.defaultTimeout = defaultTimeout;
40     }
41 
42     /**
43      * Shortcut for findClient with default timeout.
44      */
findClient(IDevice device, String processName, int processPid)45     public Client findClient(IDevice device, String processName, int processPid) {
46         return findClient(device, processName, processPid, defaultTimeout);
47     }
48 
49     /**
50      * Find the client with the given process name or process id. The name takes precedence over
51      * the process id (if valid). Stop looking after the given timeout.
52      *
53      * @param device The device to communicate with.
54      * @param processName The name of the process. May be null.
55      * @param processPid The pid of the process. Values less than or equal to zero are ignored.
56      * @param timeout The amount of milliseconds to wait, at most.
57      * @return The client, if found. Otherwise null.
58      */
findClient(IDevice device, String processName, int processPid, int timeout)59     public Client findClient(IDevice device, String processName, int processPid, int timeout) {
60         WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
61         return wfc.get();
62     }
63 
64     /**
65      * Shortcut for findAllClients with default timeout.
66      */
findAllClients(IDevice device)67     public Client[] findAllClients(IDevice device) {
68         return findAllClients(device, defaultTimeout);
69     }
70 
71     /**
72      * Retrieve all clients known to the given device. Wait at most the given timeout.
73      *
74      * @param device The device to investigate.
75      * @param timeout The amount of milliseconds to wait, at most.
76      * @return An array of clients running on the given device. May be null depending on the
77      *         device implementation.
78      */
findAllClients(IDevice device, int timeout)79     public Client[] findAllClients(IDevice device, int timeout) {
80         if (device.hasClients()) {
81             return device.getClients();
82         }
83         WaitForClients wfc = new WaitForClients(device, timeout);
84         return wfc.get();
85     }
86 
87     private static class WaitForClient implements IClientChangeListener {
88 
89         private IDevice device;
90         private String processName;
91         private int processPid;
92         private long timeout;
93         private Client result;
94 
WaitForClient(IDevice device, String processName, int processPid, long timeout)95         public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
96             this.device = device;
97             this.processName = processName;
98             this.processPid = processPid;
99             this.timeout = timeout;
100             this.result = null;
101         }
102 
get()103         public Client get() {
104             synchronized (this) {
105                 AndroidDebugBridge.addClientChangeListener(this);
106 
107                 // Maybe it's already there.
108                 if (result == null) {
109                     result = searchForClient(device);
110                 }
111 
112                 if (result == null) {
113                     try {
114                         wait(timeout);
115                     } catch (InterruptedException e) {
116                         // Note: doesn't guard for spurious wakeup.
117                     }
118                 }
119             }
120 
121             AndroidDebugBridge.removeClientChangeListener(this);
122             return result;
123         }
124 
searchForClient(IDevice device)125         private Client searchForClient(IDevice device) {
126             if (processName != null) {
127                 Client tmp = device.getClient(processName);
128                 if (tmp != null) {
129                     return tmp;
130                 }
131             }
132             if (processPid > 0) {
133                 String name = device.getClientName(processPid);
134                 if (name != null && !name.isEmpty()) {
135                     Client tmp = device.getClient(name);
136                     if (tmp != null) {
137                         return tmp;
138                     }
139                 }
140             }
141             if (processPid > 0) {
142                 // Try manual search.
143                 for (Client cl : device.getClients()) {
144                     if (cl.getClientData().getPid() == processPid
145                             && cl.getClientData().getClientDescription() != null) {
146                         return cl;
147                     }
148                 }
149             }
150             return null;
151         }
152 
isTargetClient(Client c)153         private boolean isTargetClient(Client c) {
154             if (processPid > 0 && c.getClientData().getPid() == processPid) {
155                 return true;
156             }
157             if (processName != null
158                     && processName.equals(c.getClientData().getClientDescription())) {
159                 return true;
160             }
161             return false;
162         }
163 
164         @Override
clientChanged(Client arg0, int arg1)165         public void clientChanged(Client arg0, int arg1) {
166             synchronized (this) {
167                 if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
168                     if (isTargetClient(arg0)) {
169                         result = arg0;
170                         notifyAll();
171                     }
172                 }
173             }
174         }
175     }
176 
177     private static class WaitForClients implements IClientChangeListener {
178 
179         private IDevice device;
180         private long timeout;
181 
WaitForClients(IDevice device, long timeout)182         public WaitForClients(IDevice device, long timeout) {
183             this.device = device;
184             this.timeout = timeout;
185         }
186 
get()187         public Client[] get() {
188             synchronized (this) {
189                 AndroidDebugBridge.addClientChangeListener(this);
190 
191                 if (device.hasClients()) {
192                     return device.getClients();
193                 }
194 
195                 try {
196                     wait(timeout); // Note: doesn't guard for spurious wakeup.
197                 } catch (InterruptedException exc) {
198                 }
199 
200                 // We will be woken up when the first client data arrives. Sleep a little longer
201                 // to give (hopefully all of) the rest of the clients a chance to become available.
202                 // Note: a loop with timeout is brittle as well and complicated, just accept this
203                 //       for now.
204                 try {
205                     Thread.sleep(500);
206                 } catch (InterruptedException exc) {
207                 }
208             }
209 
210             AndroidDebugBridge.removeClientChangeListener(this);
211 
212             return device.getClients();
213         }
214 
215         @Override
clientChanged(Client arg0, int arg1)216         public void clientChanged(Client arg0, int arg1) {
217             synchronized (this) {
218                 if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
219                     notifyAll();
220                 }
221             }
222         }
223     }
224 }
225