/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.preload;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
/**
* Helper class for common communication with a Client (the ddms name for a running application).
*
* Instances take a default timeout parameter that's applied to all functions without explicit
* timeout. Timeouts are in milliseconds.
*/
public class ClientUtils {
private int defaultTimeout;
public ClientUtils() {
this(10000);
}
public ClientUtils(int defaultTimeout) {
this.defaultTimeout = defaultTimeout;
}
/**
* Shortcut for findClient with default timeout.
*/
public Client findClient(IDevice device, String processName, int processPid) {
return findClient(device, processName, processPid, defaultTimeout);
}
/**
* Find the client with the given process name or process id. The name takes precedence over
* the process id (if valid). Stop looking after the given timeout.
*
* @param device The device to communicate with.
* @param processName The name of the process. May be null.
* @param processPid The pid of the process. Values less than or equal to zero are ignored.
* @param timeout The amount of milliseconds to wait, at most.
* @return The client, if found. Otherwise null.
*/
public Client findClient(IDevice device, String processName, int processPid, int timeout) {
WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
return wfc.get();
}
/**
* Shortcut for findAllClients with default timeout.
*/
public Client[] findAllClients(IDevice device) {
return findAllClients(device, defaultTimeout);
}
/**
* Retrieve all clients known to the given device. Wait at most the given timeout.
*
* @param device The device to investigate.
* @param timeout The amount of milliseconds to wait, at most.
* @return An array of clients running on the given device. May be null depending on the
* device implementation.
*/
public Client[] findAllClients(IDevice device, int timeout) {
if (device.hasClients()) {
return device.getClients();
}
WaitForClients wfc = new WaitForClients(device, timeout);
return wfc.get();
}
private static class WaitForClient implements IClientChangeListener {
private IDevice device;
private String processName;
private int processPid;
private long timeout;
private Client result;
public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
this.device = device;
this.processName = processName;
this.processPid = processPid;
this.timeout = timeout;
this.result = null;
}
public Client get() {
synchronized (this) {
AndroidDebugBridge.addClientChangeListener(this);
// Maybe it's already there.
if (result == null) {
result = searchForClient(device);
}
if (result == null) {
try {
wait(timeout);
} catch (InterruptedException e) {
// Note: doesn't guard for spurious wakeup.
}
}
}
AndroidDebugBridge.removeClientChangeListener(this);
return result;
}
private Client searchForClient(IDevice device) {
if (processName != null) {
Client tmp = device.getClient(processName);
if (tmp != null) {
return tmp;
}
}
if (processPid > 0) {
String name = device.getClientName(processPid);
if (name != null && !name.isEmpty()) {
Client tmp = device.getClient(name);
if (tmp != null) {
return tmp;
}
}
}
if (processPid > 0) {
// Try manual search.
for (Client cl : device.getClients()) {
if (cl.getClientData().getPid() == processPid
&& cl.getClientData().getClientDescription() != null) {
return cl;
}
}
}
return null;
}
private boolean isTargetClient(Client c) {
if (processPid > 0 && c.getClientData().getPid() == processPid) {
return true;
}
if (processName != null
&& processName.equals(c.getClientData().getClientDescription())) {
return true;
}
return false;
}
@Override
public void clientChanged(Client arg0, int arg1) {
synchronized (this) {
if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
if (isTargetClient(arg0)) {
result = arg0;
notifyAll();
}
}
}
}
}
private static class WaitForClients implements IClientChangeListener {
private IDevice device;
private long timeout;
public WaitForClients(IDevice device, long timeout) {
this.device = device;
this.timeout = timeout;
}
public Client[] get() {
synchronized (this) {
AndroidDebugBridge.addClientChangeListener(this);
if (device.hasClients()) {
return device.getClients();
}
try {
wait(timeout); // Note: doesn't guard for spurious wakeup.
} catch (InterruptedException exc) {
}
// We will be woken up when the first client data arrives. Sleep a little longer
// to give (hopefully all of) the rest of the clients a chance to become available.
// Note: a loop with timeout is brittle as well and complicated, just accept this
// for now.
try {
Thread.sleep(500);
} catch (InterruptedException exc) {
}
}
AndroidDebugBridge.removeClientChangeListener(this);
return device.getClients();
}
@Override
public void clientChanged(Client arg0, int arg1) {
synchronized (this) {
if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
notifyAll();
}
}
}
}
}