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

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

/*
 * 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.
 */
package org.arquillian.droidium.container.impl;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.arquillian.droidium.container.api.AndroidBridge;
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.configuration.AndroidSDK;
import org.arquillian.droidium.container.configuration.Command;
import org.arquillian.droidium.container.execution.CountDownWatch;
import org.arquillian.droidium.container.execution.ProcessExecution;
import org.arquillian.droidium.container.execution.ProcessExecutor;
import org.arquillian.droidium.container.execution.ProcessInteractionBuilder;
import org.arquillian.droidium.container.spi.event.AndroidDeviceReady;
import org.arquillian.droidium.container.spi.event.AndroidVirtualDeviceAvailable;
import org.arquillian.droidium.container.utils.NetUtils;
import org.jboss.arquillian.container.spi.context.annotation.ContainerScoped;
import org.jboss.arquillian.core.api.Event;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.InstanceProducer;
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;

/**
 * Starts an emulator and either connects to an existing device or creates one. 
*
* Observes: *
    *
  • {@link AndroidVirtualDeviceAvailable}
  • *
* * Creates: *
    *
  • {@link AndroidEmulator}
  • *
  • {@link AndroidDevice}
  • *
* * Fires: *
    *
  • {@link AndroidDeviceReady}
  • *
* * @author Karel Piwko * @author Stefan Miklosovic * */ public class AndroidEmulatorStartup { private static final Logger logger = Logger.getLogger(AndroidEmulatorStartup.class.getName()); @Inject @ContainerScoped private InstanceProducer androidEmulator; @Inject @ContainerScoped private InstanceProducer androidDevice; @Inject private Instance androidBridge; @Inject private Instance executor; @Inject private Instance configuration; @Inject private Instance androidSDK; @Inject private Event androidDeviceReady; public void startAndroidEmulator(@Observes AndroidVirtualDeviceAvailable event) throws AndroidExecutionException { if (!androidBridge.get().isConnected()) { throw new IllegalStateException("Android debug bridge must be connected in order to spawn the emulator"); } logger.log(Level.INFO, "Starting Android emulator of AVD name {0}.", configuration.get().getAvdName()); AndroidContainerConfiguration configuration = this.configuration.get(); AndroidDevice emulator = null; CountDownWatch countdown = new CountDownWatch(configuration.getEmulatorBootupTimeoutInSeconds(), TimeUnit.SECONDS); logger.log(Level.INFO, "Waiting {0} seconds for emulator {1} to be started and connected.", new Object[] { countdown.timeout(), configuration.getAvdName() }); ProcessExecutor emulatorProcessExecutor = this.executor.get(); DeviceConnectDiscovery deviceDiscovery = new DeviceConnectDiscovery(); AndroidDebugBridge.addDeviceChangeListener(deviceDiscovery); ProcessExecution emulatorExecution = startEmulator(emulatorProcessExecutor); androidEmulator.set(new AndroidEmulator(emulatorExecution)); logger.log(Level.INFO, "Emulator process started, {0} seconds remaining to start the device {1}", new Object[] { countdown.timeLeft(), configuration.getAvdName() }); waitUntilBootUpIsComplete(deviceDiscovery, emulatorExecution, emulatorProcessExecutor, countdown); unlockEmulator(deviceDiscovery, emulatorProcessExecutor); emulator = deviceDiscovery.getDiscoveredDevice(); setDronePorts(emulator); AndroidDebugBridge.removeDeviceChangeListener(deviceDiscovery); androidDevice.set(emulator); androidDeviceReady.fire(new AndroidDeviceReady(emulator)); } private void setDronePorts(AndroidDevice device) { device.setDroneHostPort(configuration.get().getDroneHostPort()); device.setDroneGuestPort(configuration.get().getDroneGuestPort()); } private ProcessExecution startEmulator(ProcessExecutor executor) throws AndroidExecutionException { AndroidSDK sdk = this.androidSDK.get(); AndroidContainerConfiguration configuration = this.configuration.get(); Command command = new Command(); command.add(sdk.getEmulatorPath()) .add("-avd") .add(configuration.getAvdName()); if (configuration.getSdCard() != null) { command.add("-sdcard"); command.add(configuration.getSdCard()); } if (configuration.getConsolePort() != null) { if (!NetUtils.isPortFree(configuration.getConsolePort())) { throw new AndroidExecutionException("It seems there is already something which listens on specified " + "console port " + configuration.getConsolePort() + " so Droidium can not start emulator there."); } } if (configuration.getAdbPort() != null) { if (!NetUtils.isPortFree(configuration.getAdbPort())) { throw new AndroidExecutionException("It seems there is already something which listens on specified " + "adb port " + configuration.getAdbPort() + " so Droidium can not start emulator there."); } } if (configuration.getConsolePort() != null && configuration.getAdbPort() != null) { command.add("-ports") .add(configuration.getConsolePort() + "," + configuration.getAdbPort()); } else if (configuration.getConsolePort() != null) { command.add("-port").add(configuration.getConsolePort()); } command.addTokenized(configuration.getEmulatorOptions()); logger.log(Level.INFO, "Starting emulator \"{0}\", using {1}", new Object[] { configuration.getAvdName(), command }); // define what patterns would be consider worthy to notify user ProcessInteractionBuilder interactions = new ProcessInteractionBuilder() .errors("^SDL init failure.*$") .errors("^PANIC:.*$") .errors("^error.*$"); // execute emulator try { return executor.spawn(interactions.build(), command.getAsArray()); } catch (AndroidExecutionException e) { throw new AndroidExecutionException(e, "Unable to start emulator \"{0}\", using {1}", configuration.getAvdName(), command); } } private void unlockEmulator(final DeviceConnectDiscovery deviceDiscovery, final ProcessExecutor executor) { final AndroidDevice connectedDevice = deviceDiscovery.getDiscoveredDevice(); final String serialNumber = connectedDevice.getSerialNumber(); Command unlockPart1 = new Command(androidSDK.get().getAdbPath(), "-s", serialNumber, "shell", "input", "keyevent", "82"); Command unlockPart2 = new Command(androidSDK.get().getAdbPath(), "-s", serialNumber, "shell", "input", "keyevent", "4"); try { executor.execute(ProcessInteractionBuilder.NO_INTERACTION, unlockPart1.getAsArray()); executor.execute(ProcessInteractionBuilder.NO_INTERACTION, unlockPart2.getAsArray()); } catch (final AndroidExecutionException e) { logger.log(Level.WARNING, "Unlocking device failed", e); // exception is ignored, logged only } } private void waitUntilBootUpIsComplete(final DeviceConnectDiscovery deviceDiscovery, final ProcessExecution emulatorExecution, final ProcessExecutor executor, final CountDownWatch countdown) throws AndroidExecutionException { try { boolean isOnline = executor.scheduleUntilTrue(new Callable() { @Override public Boolean call() throws Exception { // ARQ-1583 check status of emulator // ARQ-1602 on windows, emulator process might return. In such case we need to check also the exit value if (emulatorExecution.isFinished() && emulatorExecution.executionFailed()) { throw new IllegalStateException("Emulator device startup exited prematurely with exit code " + emulatorExecution.getExitCode()); } return deviceDiscovery.isOnline(); } }, countdown.timeLeft(), countdown.getTimeUnit().convert(1, TimeUnit.SECONDS), countdown.getTimeUnit()); if (isOnline == false) { throw new IllegalStateException( "No emulator device was brough online during " + countdown.timeout() + " seconds to Android Debug Bridge. Please increase the time limit in order to get emulator connected."); } // device is connected to ADB final AndroidDevice connectedDevice = deviceDiscovery.getDiscoveredDevice(); final String adbPath = androidSDK.get().getAdbPath(); logger.log(Level.INFO, "ADB path: " + adbPath); final String serialNumber = connectedDevice.getSerialNumber(); logger.log(Level.INFO, "Serial number: " + serialNumber); isOnline = executor.scheduleUntilTrue(new Callable() { @Override public Boolean call() throws Exception { // ARQ-1583 check status of emulator // ARQ-1602 on windows, emulator process might return. In such case we need to check also the exit value if (emulatorExecution.isFinished() && emulatorExecution.executionFailed()) { throw new IllegalStateException("Emulator device startup exited prematurely with exit code " + emulatorExecution.getExitCode()); } // check properties of underlying process Command propertiesCheck = new Command(adbPath, "-s", serialNumber, "shell", "getprop"); ProcessExecution properties = executor.execute(ProcessInteractionBuilder.NO_INTERACTION, propertiesCheck.getAsArray()); for (String line : properties.getOutput()) { if (line.contains("[ro.runtime.firstboot]")) { // boot is completed return true; } } return false; } }, countdown.timeLeft(), countdown.getTimeUnit().convert(1, TimeUnit.SECONDS), countdown.getTimeUnit()); if (logger.isLoggable(Level.INFO)) { logger.log(Level.INFO, "Android emulator {0} was started within {1} seconds", new Object[] { connectedDevice.getAvdName(), countdown.timeElapsed() }); } if (isOnline == false) { throw new AndroidExecutionException("Emulator device hasn't started properly in " + countdown.timeout() + " seconds. Please increase the time limit in order to get emulator booted."); } } catch (InterruptedException e) { throw new AndroidExecutionException(e, "Emulator device startup failed."); } catch (ExecutionException e) { logger.log(Level.INFO, e.getCause().toString()); throw new AndroidExecutionException(e, "Emulator device startup failed."); } } private static class DeviceConnectDiscovery implements IDeviceChangeListener { private IDevice discoveredDevice; private boolean online; @Override public void deviceChanged(IDevice device, int changeMask) { if (discoveredDevice.equals(device) && (changeMask & IDevice.CHANGE_STATE) == 1) { if (device.isOnline()) { this.online = true; } } } @Override public void deviceConnected(IDevice device) { this.discoveredDevice = device; logger.log(Level.INFO, "Discovered an emulator device id={0} connected to ADB bus", device.getSerialNumber()); } @Override public void deviceDisconnected(IDevice device) { } public AndroidDevice getDiscoveredDevice() { return new AndroidDeviceImpl(discoveredDevice); } public boolean isOnline() { return online; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy