/* * 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(); } } } } }