package com.mobilesorcery.sdk.builder.android.launch; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import com.mobilesorcery.sdk.builder.android.Activator; import com.mobilesorcery.sdk.builder.android.launch.Emulator.IAndroidEmulatorProcess; import com.mobilesorcery.sdk.core.AbstractTool; import com.mobilesorcery.sdk.core.CollectingLineHandler; import com.mobilesorcery.sdk.core.MoSyncTool; import com.mobilesorcery.sdk.core.Util; public class Emulator extends AbstractTool { public static interface IAndroidEmulatorProcess { /** * Returns the avd associated with this process. */ public String getAVD(); /** * Returns the emulator identifier associated with this process. * @return */ public String getEmulatorId(); /** * Waits for this emulator to be booted and ready for uploading APKs * @param timeout * @param unit * @throws CoreException If a timeout occurred, or if some other error occurred. */ public void awaitEmulatorStarted(int timeout, TimeUnit unit) throws CoreException; /** * Returns the underlying native process for this emulator. * @return */ public Process getNativeProcess(); } private class AndroidEmulatorProcess extends CollectingLineHandler implements IAndroidEmulatorProcess { private final String avd; private String emulatorId; private final List<String> initialEmulators; private final Emulator emulator; private boolean started; private Process process; AndroidEmulatorProcess(Emulator emulator, String avd) throws CoreException { this.avd = avd; this.emulator = emulator; initialEmulators = ADB.getExternal().listEmulators(false); emulator.associate(avd, this); } @Override public void start(Process process) { this.process = process; super.start(process); } @Override public void stop(IOException e) { emulator.disassociate(avd, this); super.stop(e); } @Override public String getAVD() { return avd; } @Override public String getEmulatorId() { return emulatorId; } @Override public synchronized void awaitEmulatorStarted(int timeout, TimeUnit unit) throws CoreException { if (started) { return; } awaitEmulatorStarted(ADB.getExternal(), this, timeout, unit); started = true; } public Process getNativeProcess() { return process; } private void awaitEmulatorStarted(ADB adb, CollectingLineHandler emulatorProcess, int timeout, TimeUnit unit) throws CoreException { long now = System.currentTimeMillis(); long timeoutInMs = TimeUnit.MILLISECONDS.convert(timeout, unit); boolean wasStopped = emulatorProcess.isStopped(); while (!wasStopped && System.currentTimeMillis() - now < timeoutInMs) { List<String> recentlyStarted = adb.listEmulators(false); recentlyStarted.removeAll(initialEmulators); if (recentlyStarted.size() > 0) { setEmulatorId(recentlyStarted.get(0)); adb.awaitBoot(emulatorId, TimeUnit.MILLISECONDS.convert(2, TimeUnit.MINUTES)); return; } try { Thread.sleep(5000); } catch (InterruptedException e) { break; } wasStopped = emulatorProcess.isStopped(); } if (!emulatorProcess.isStopped()) { throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Timeout occurred -- could not connect to Android Emulator")); } else { throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Could not launch Android Emulator; wrong arguments?")); } } private void setEmulatorId(String emulatorId) { this.emulatorId = emulatorId; } } private final HashMap<String, List<IAndroidEmulatorProcess>> avdToProcess = new HashMap<String, List<IAndroidEmulatorProcess>>(); public Emulator(IPath pathToEmulator) { super(pathToEmulator); } private static IPath externalSDKPath = null; private static Emulator externalEmulator = null; public synchronized AndroidEmulatorProcess start(String avd, boolean fork, String... options) throws CoreException { String[] commandLine = new String[options.length + 3]; commandLine[0] = getToolPath().getAbsolutePath(); commandLine[1] = "-avd"; commandLine[2] = avd; System.arraycopy(options, 0, commandLine, 3, options.length); AndroidEmulatorProcess result = new AndroidEmulatorProcess(this, avd); execute(commandLine, result, null, fork); return result; } public static Emulator getExternal() { IPath sdkPath = Activator.getDefault().getExternalAndroidSDKPath(); if (externalEmulator == null || !Util.equals(sdkPath, externalSDKPath)) { externalSDKPath = sdkPath; externalEmulator = new Emulator(sdkPath == null ? null : sdkPath.append("tools/emulator" + MoSyncTool.getBinExtension())); } return externalEmulator; } synchronized void associate(String avd, IAndroidEmulatorProcess emulatorProcess) { List<IAndroidEmulatorProcess> processes = avdToProcess.get(avd); if (processes == null) { processes = new ArrayList<IAndroidEmulatorProcess>(); avdToProcess.put(avd, processes); } processes.add(emulatorProcess); } synchronized void disassociate(String avd, IAndroidEmulatorProcess emulatorProcess) { List<IAndroidEmulatorProcess> processes = avdToProcess.get(avd); if (processes != null) { processes.remove(emulatorProcess); } if (processes.isEmpty()) { avdToProcess.remove(avd); } } public synchronized List<IAndroidEmulatorProcess> getRunningProcesses(String avd) { List<IAndroidEmulatorProcess> runningProcesses = avdToProcess.get(avd); if (runningProcesses == null) { runningProcesses = new ArrayList<Emulator.IAndroidEmulatorProcess>(); } return Collections.unmodifiableList(runningProcesses); } public synchronized List<IAndroidEmulatorProcess> getAllRunningProcesses() { List<IAndroidEmulatorProcess> allProcesses = new ArrayList<Emulator.IAndroidEmulatorProcess>(); for (String avd : avdToProcess.keySet()) { List<IAndroidEmulatorProcess> processes = avdToProcess.get(avd); allProcesses.addAll(processes); } return allProcesses; } @Override protected String getToolName() { return "Android Emulator"; } }