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