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

org.arquillian.droidium.container.impl.AndroidEmulatorShutdown Maven / Gradle / Ivy

There is a newer version: 1.0.1.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.
 *
 * Copyright (C) 2009, 2010 Jayway AB
 *
 * 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 org.arquillian.droidium.container.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.arquillian.droidium.container.api.AndroidDevice;
import org.arquillian.droidium.container.api.AndroidExecutionException;
import org.arquillian.droidium.container.configuration.AndroidContainerConfiguration;
import org.arquillian.droidium.container.execution.CountDownWatch;
import org.arquillian.droidium.container.execution.ProcessExecutor;
import org.arquillian.droidium.container.spi.event.AndroidContainerStop;
import org.arquillian.droidium.container.spi.event.AndroidEmulatorShuttedDown;
import org.arquillian.droidium.container.spi.event.AndroidVirtualDeviceDelete;
import org.jboss.arquillian.core.api.Event;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.core.api.annotation.Observes;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;

/**
 * Brings Android Emulator down. 
*
* Observes: *
    *
  • {@link AndroidContainerStop}
  • *
* * Fires: *
    *
  • {@link AndroidEmulatorShuttedDown}
  • *
  • {@link AndroidVirtualDeviceDelete}
  • *
* * @author Karel Piwko * @author Manfred Moser */ public class AndroidEmulatorShutdown { private static final Logger logger = Logger.getLogger(AndroidEmulatorShutdown.class.getName()); @Inject private Instance configuration; @Inject private Instance androidDevice; @Inject private Instance androidEmulator; @Inject private Instance executor; @Inject private Event androidEmulatorShuttedDown; @Inject private Event androidVirtualDeviceDelete; public void shutdownEmulator(@Observes AndroidContainerStop event) throws AndroidExecutionException { AndroidEmulator emulator = androidEmulator.get(); AndroidDevice device = androidDevice.get(); AndroidContainerConfiguration configuration = this.configuration.get(); if (emulator != null && device.isEmulator()) { logger.log(Level.INFO, "Stopping Android emulator of AVD name {0}.", configuration.getAvdName()); final ProcessExecutor executor = this.executor.get(); final Process p = emulator.getProcess().getProcess(); CountDownWatch countdown = new CountDownWatch(configuration.getEmulatorShutdownTimeoutInSeconds(), TimeUnit.SECONDS); logger.info("Waiting " + countdown.timeout() + " seconds for emulator " + device.getAvdName() + " to be disconnected and shutdown."); try { final DeviceDisconnectDiscovery listener = new DeviceDisconnectDiscovery(device); AndroidDebugBridge.addDeviceChangeListener(listener); stopEmulator(p, executor, device, countdown); waitUntilShutDownIsComplete(device, listener, executor, countdown); AndroidDebugBridge.removeDeviceChangeListener(listener); if (configuration.isAVDGenerated()) { androidVirtualDeviceDelete.fire(new AndroidVirtualDeviceDelete()); } androidEmulatorShuttedDown.fire(new AndroidEmulatorShuttedDown(device)); } finally { executor.removeShutdownHook(p); } } } /** * Brings Android device down. * * @param device {@link AndroidDevice} to shut down * @param listener * @param executor * @param countdown * @throws AndroidExecutionException */ private void waitUntilShutDownIsComplete(final AndroidDevice device, final DeviceDisconnectDiscovery listener, ProcessExecutor executor, CountDownWatch countdown) throws AndroidExecutionException { try { // wait until device is disconnected from bridge boolean isOffline = executor.scheduleUntilTrue(new Callable() { @Override public Boolean call() throws Exception { return listener.isOffline(); } }, countdown.timeLeft(), countdown.getTimeUnit().convert(1, TimeUnit.SECONDS), countdown.getTimeUnit()); if (isOffline == false) { throw new AndroidExecutionException("Unable to disconnect AVD device {0} in given timeout {1} seconds", device.getAvdName(), countdown.timeout()); } logger.info("Device " + device.getAvdName() + " on port " + device.getConsolePort() + " was disconnected in " + countdown.timeElapsed() + " seconds."); } catch (InterruptedException e) { throw new AndroidExecutionException(e, "Unable to disconnect AVD device {0}", device.getAvdName()); } catch (ExecutionException e) { throw new AndroidExecutionException(e, "Unable to disconnect AVD device {0}", device.getAvdName()); } } /** * This method contains the code required to stop an emulator. * * @return {@code true} if stopped without errors, {@code false} otherwise * @param device The device to stop */ private Boolean stopEmulator(final Process p, final ProcessExecutor executor, final AndroidDevice device, final CountDownWatch countdown) throws AndroidExecutionException { int devicePort = extractPortFromDevice(device); if (devicePort == -1) { logger.severe("Unable to retrieve port to stop emulator " + device.getSerialNumber() + "."); return false; } else { logger.info("Stopping emulator " + device.getSerialNumber() + " via port " + devicePort + "."); try { Boolean stopped = executor.submit(sendEmulatorCommand(devicePort, "kill")).get(countdown.timeLeft(), countdown.getTimeUnit()); // wait to retrieve finished process of emulator int retval = executor.submit(new Callable() { @Override public Integer call() throws Exception { return p.waitFor(); } }).get(5, TimeUnit.SECONDS); return stopped && retval == 0; } catch (TimeoutException e) { p.destroy(); logger.warning("Emulator process was forcibly destroyed, " + countdown.timeLeft() + " seconds remaining to dispose the device"); return false; } catch (InterruptedException e) { p.destroy(); throw new AndroidExecutionException(e, "Unable to stop emulator {0}", device.getAvdName()); } catch (ExecutionException e) { p.destroy(); throw new AndroidExecutionException(e, "Unable to stop emulator {0}", device.getAvdName()); } } } /** * This method extracts a port number from the serial number of a device. It assumes that the device name is of format * [xxxx-nnnn] where nnnn is the port number. * * @param device The device to extract the port number from. * @return Returns the port number of the device */ private int extractPortFromDevice(AndroidDevice device) { String portStr = device.getSerialNumber().substring(device.getSerialNumber().lastIndexOf("-") + 1); if (portStr != null && portStr.length() > 0) { try { return Integer.parseInt(portStr); } catch (NumberFormatException e) { return -1; } } // If the port is not available then return -1 return -1; } /** * Sends a user command to the running emulator via its telnet interface. * * @param port The emulator's telnet port. * @param command The command to execute on the emulator's telnet interface. * @return Whether sending the command succeeded. */ private Callable sendEmulatorCommand(final int port, final String command) { return new Callable() { @Override public Boolean call() throws IOException { Socket socket = null; BufferedReader in = null; PrintWriter out = null; try { socket = new Socket("127.0.0.1", port); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); String telnetOutputString = null; while ((telnetOutputString = in.readLine()) != null) { if (telnetOutputString.equals("OK")) { break; } } out.write(command); out.write("\n"); out.flush(); } finally { try { out.close(); in.close(); socket.close(); } catch (Exception e) { // ignore } } return true; } }; } private static class DeviceDisconnectDiscovery implements IDeviceChangeListener { private boolean offline; private final AndroidDevice connectedDevice; public DeviceDisconnectDiscovery(AndroidDevice connectedDevice) { this.connectedDevice = connectedDevice; } @Override public void deviceChanged(IDevice device, int changeMask) { } @Override public void deviceDisconnected(IDevice device) { if (device.getAvdName().equals(connectedDevice.getAvdName())) { this.offline = true; } logger.fine("Discovered an emulator device id=" + device.getSerialNumber() + " disconnected from ADB bus"); } public boolean isOffline() { return offline; } @Override public void deviceConnected(IDevice device) { } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy