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