All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.selendroid.server.model.DeviceStore Maven / Gradle / Ivy

/*
 * Copyright 2012-2013 eBay Software Foundation and selendroid committers.
 * 
 * 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 io.selendroid.server.model;

import io.selendroid.SelendroidCapabilities;
import io.selendroid.android.AndroidApp;
import io.selendroid.android.AndroidDevice;
import io.selendroid.android.AndroidEmulator;
import io.selendroid.android.AndroidEmulatorPowerStateListener;
import io.selendroid.android.DeviceManager;
import io.selendroid.android.HardwareDeviceListener;
import io.selendroid.android.impl.DefaultAndroidEmulator;
import io.selendroid.android.impl.DefaultHardwareDevice;
import io.selendroid.device.DeviceTargetPlatform;
import io.selendroid.exceptions.AndroidDeviceException;
import io.selendroid.exceptions.AndroidSdkException;
import io.selendroid.exceptions.DeviceStoreException;
import io.selendroid.exceptions.SelendroidException;
import io.selendroid.server.model.impl.DefaultPortFinder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DeviceStore {
  private static final Logger log = Logger.getLogger(DeviceStore.class.getName());
  private List devicesInUse = new ArrayList();
  private Map> androidDevices =
      new HashMap>();
  private EmulatorPortFinder androidEmulatorPortFinder = null;
  private boolean clearData = true;
  private AndroidEmulatorPowerStateListener emulatorPowerStateListener = null;
  private DeviceManager deviceManager = null;

  public DeviceStore(Integer emulatorPort, DeviceManager deviceManager) {
    this.deviceManager = deviceManager;
    androidEmulatorPortFinder = new DefaultPortFinder(emulatorPort, emulatorPort + 30);
  }

  public DeviceStore(EmulatorPortFinder androidEmulatorPortFinder,
      DeviceManager deviceManager) {
    this.deviceManager = deviceManager;
    this.androidEmulatorPortFinder = androidEmulatorPortFinder;
  }

  public Integer nextEmulatorPort() {
    return androidEmulatorPortFinder.next();
  }

  /**
   * After a test session a device should be released. That means id will be removed from the list
   * of devices in use and in case of an emulator it will be stopped.
   * 
   * @param device The device to release
   * @see {@link #findAndroidDevice(SelendroidCapabilities)}
   */
  public void release(AndroidDevice device, AndroidApp aut) {
    if (devicesInUse.contains(device)) {
      // stop the app anyway - better in case people do use snapshots
      try {
        device.kill(aut);
      } catch (Exception e) {
        e.printStackTrace();
      }
      if (clearData) {
        try {
          device.clearUserData(aut);
        } catch (AndroidSdkException e) {
          e.printStackTrace();
        }
      }
      if (device instanceof AndroidEmulator) {
        AndroidEmulator emulator = (AndroidEmulator) device;
        try {
          emulator.stop();
        } catch (AndroidDeviceException e) {
          log.severe("Failed to stop emulator: " + e.getMessage());
        }
        androidEmulatorPortFinder.release(emulator.getPort());
      }
      devicesInUse.remove(device);
    }
  }

  /* package */void initAndroidDevices(HardwareDeviceListener hardwareDeviceListener,
      boolean shouldKeepAdbAlive) throws AndroidDeviceException {
    emulatorPowerStateListener = new DefaultEmulatorPowerStateListener();
    deviceManager.initialize(hardwareDeviceListener, emulatorPowerStateListener);

    List emulators = DefaultAndroidEmulator.listAvailableAvds();
    addEmulators(emulators);

    if (getDevices().isEmpty()) {
      SelendroidException e =
          new SelendroidException(
              "No android virtual devices were found. "
                  + "Please start the android tool and create emulators and restart the selendroid-standalone "
                  + "or plugin an Android hardware device via USB.");
      log.warning("Warning: " + e);
    }
  }


  public synchronized void addDevice(AndroidDevice androidDevice) throws AndroidDeviceException {
    if (androidDevice == null) {
      log.info("No Android devices were found.");
      return;
    }
    if (androidDevice instanceof AndroidEmulator) {
      throw new AndroidDeviceException(
          "For adding emulator instances please use #addEmulator method.");
    }
    if (androidDevice.isDeviceReady() == true) {
      log.info("Adding: " + androidDevice);
      addDeviceToStore(androidDevice);
    }
  }

  public void addEmulators(List emulators) throws AndroidDeviceException {
    if (emulators == null || emulators.isEmpty()) {
      log.info("No emulators has been found.");
      return;
    }
    for (AndroidEmulator emulator : emulators) {
      log.info("Adding: " + emulator);
      addDeviceToStore((AndroidDevice) emulator);
    }
  }


  /**
   * Internal method to add an actual device to the store.
   * 
   * @param device The device to add.
   * @throws AndroidDeviceException
   */
  protected synchronized void addDeviceToStore(AndroidDevice device) throws AndroidDeviceException {
    if (androidDevices.containsKey(device.getTargetPlatform())) {
      if (androidDevices.get(device.getTargetPlatform()) == null) {
        androidDevices.put(device.getTargetPlatform(), new ArrayList());
      }
      androidDevices.get(device.getTargetPlatform()).add((AndroidDevice) device);
    } else {
      List devices = new ArrayList();
      devices.add((AndroidDevice) device);
      androidDevices.put(device.getTargetPlatform(), devices);
    }
  }

  /**
   * Finds a device for the requested capabilities. important note: if the device is not any
   * longer used, call the {@link #release(AndroidDevice, AndroidApp)} method.
   * 
   * @param caps The desired test session capabilities.
   * @return Matching device for a test session.
   * @throws DeviceStoreException
   * @see {@link #release(AndroidDevice, AndroidApp)}
   */
  public synchronized AndroidDevice findAndroidDevice(SelendroidCapabilities caps)
      throws DeviceStoreException {
    if (caps == null) {
      throw new IllegalArgumentException("Error: capabilities are null");
    }
    if (androidDevices.isEmpty()) {
      throw new DeviceStoreException(
          "Fatal Error: Device Store does not contain any Android Device.");
    }
    String platformVersion = caps.getPlatformVersion();
    
    List devices = null;
    if (platformVersion == null || platformVersion.isEmpty()) {
      devices = new ArrayList();
      for (List list : androidDevices.values()) {
        devices.addAll(list);
      }
    } else {
      DeviceTargetPlatform platform = DeviceTargetPlatform.fromPlatformVersion(platformVersion);
      devices = androidDevices.get(platform);
    }
    if (devices == null) {
      devices = new ArrayList();
    }

    // keep a list of emulators that aren't started to be used as backup
    List potentialMatches = new ArrayList();
    for (AndroidDevice device : devices) {
      log.info("Evaluating if this device is a match for this session: " + device.toString());

      // logic of precedence to select a device -
      // screen size must match the capabilities, if no screen size capability sent this is ignored
      // enforce isEmulator capability request
      // enforce API level capability
      //  1) running unused device or running unused emulator
      //  2) non-running emulator

      if (device.screenSizeMatches(caps.getScreenSize())) {
        if (devicesInUse.contains(device)) {
          log.info("Device is in use.");
          continue;
        }
        if (caps.getEmulator() == null
            || (caps.getEmulator() == true && device instanceof DefaultAndroidEmulator)
            || (caps.getEmulator() == false && device instanceof DefaultHardwareDevice)) {
          String serial = caps.getSerial();
          if (serial != null && device.getSerial().equals(serial)) {
            devicesInUse.add(device);
            return device;
          }
          if (device instanceof DefaultAndroidEmulator && !((DefaultAndroidEmulator) device).isEmulatorStarted()) {
            potentialMatches.add(device);
            continue;
          }
          devicesInUse.add(device);
          return device;
        }
      }
    }
    if (potentialMatches.size() > 0) {
      log.info("Using potential match: " + potentialMatches.get(0));
      devicesInUse.add(potentialMatches.get(0));
      return potentialMatches.get(0);
    }
    throw new DeviceStoreException("No devices are found. "
        + "This can happen if the devices are in use or no device screen "
        + "matches the required capabilities.");
  }

  private boolean isEmulatorSwitchedOff(AndroidDevice device) throws DeviceStoreException {
    if (device instanceof AndroidEmulator) {
      try {
        return !((AndroidEmulator) device).isEmulatorStarted();
      } catch (AndroidDeviceException e) {
        throw new DeviceStoreException(e);
      }
    }
    return true;
  }

  public List getDevices() {
    List devices = new ArrayList();
    for (Map.Entry> entry : androidDevices.entrySet()) {
      devices.addAll(entry.getValue());
    }
    return devices;
  }

  /**
   * For testing only
   */
  /* package */List getDevicesInUse() {
    return devicesInUse;
  }

  /**
   * For testing only
   */
  /* package */Map> getDevicesList() {
    return androidDevices;
  }

  /**
   * Removes the given device from store so that it cannot be any longer be used for testing. This
   * can happen if e.g. the hardware device gets unplugged from the computer.
   * 
   * @param device the device to remove.
   * @throws DeviceStoreException when parameter is not type of 'DefaultHardwareDevice'.
   */
  public void removeAndroidDevice(AndroidDevice device) throws DeviceStoreException {
    if (device == null) {
      return;
    }
    boolean hardwareDevice = device instanceof DefaultHardwareDevice;
    if (hardwareDevice == false) {
      throw new DeviceStoreException("Only devices of type 'DefaultHardwareDevice' can be removed.");
    }

    release(device, null);
    DeviceTargetPlatform apiLevel = device.getTargetPlatform();
    if (androidDevices.containsKey(apiLevel)) {
      log.info("Removing: " + device);
      androidDevices.get(apiLevel).remove(device);
      if (androidDevices.get(apiLevel).isEmpty()) {
        androidDevices.remove(apiLevel);
      }
    } else {
      log.warning("The target platform version of the device is not found in device store.");
      log.warning("The device was propably already removed.");
    }
  }

  public void setClearData(boolean clearData) {
    this.clearData = clearData;
  }

  class DefaultEmulatorPowerStateListener implements AndroidEmulatorPowerStateListener {

    @Override
    public void onDeviceStarted(String avdName, String serial) {
      AndroidEmulator emulator = findEmulator(avdName);
      if (emulator != null) {
        Integer port = Integer.parseInt(serial.replace("emulator-", ""));
        emulator.setSerial(port);
        emulator.setWasStartedBySelendroid(false);
      }
    }

    AndroidEmulator findEmulator(String avdName) {
      for (AndroidDevice device : getDevices()) {
        if (device instanceof AndroidEmulator) {
          AndroidEmulator emulator = (AndroidEmulator) device;
          if (avdName.equals(emulator.getAvdName())) {
            return emulator;
          }
        }
      }
      return null;
    }

    @Override
    public void onDeviceStopped(String avdName) {
      // do nothing
    }

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy