/* * Copyright (C) 2012 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.motorola.studio.android.emulator.logic; import static com.motorola.studio.android.common.log.StudioLogger.debug; import static com.motorola.studio.android.common.log.StudioLogger.info; import java.io.File; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import org.eclipse.core.net.proxy.IProxyData; import org.eclipse.core.net.proxy.IProxyService; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.osgi.util.NLS; import org.eclipse.ui.IViewPart; import org.osgi.framework.ServiceReference; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.motorola.studio.android.adt.DDMSFacade; import com.motorola.studio.android.adt.SdkUtils; import com.motorola.studio.android.common.exception.AndroidException; import com.motorola.studio.android.common.log.StudioLogger; import com.motorola.studio.android.common.preferences.DialogWithToggleUtils; import com.motorola.studio.android.common.utilities.EclipseUtils; import com.motorola.studio.android.emulator.EmulatorPlugin; import com.motorola.studio.android.emulator.core.exception.InstanceStartException; import com.motorola.studio.android.emulator.core.exception.InstanceStopException; import com.motorola.studio.android.emulator.core.exception.StartCancelledException; import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; import com.motorola.studio.android.emulator.i18n.EmulatorNLS; import com.motorola.studio.android.nativeos.IDevicePropertiesOSConstants; import com.motorola.studio.android.nativeos.NativeUIUtils; @SuppressWarnings("restriction") public class StartEmulatorProcessLogic implements IAndroidLogic { /** * */ private static final String EMULATOR_NO_SNAPSHOT_LOAD = "-no-snapshot-load"; /** * */ private static final String EMULATOR_NO_SNAPSHOT_SAVE = "-no-snapshot-save"; /** * */ private static final String EMULATOR_HTTP_PROXY_PARAMETER = "-http-proxy"; // Proxy constants private static final String PROXY_AT = "@"; private static final String PROXY_COLON = ":"; private static final String PROXY_HTTP = "http://"; private static final String EMULATOR_VIEW = "com.motorola.studio.android.emulator.androidView"; // Strings used to build the command line for launching the emulator process private static final String ARM_EMULATOR_RELATIVE_PATH = "/tools/emulator-arm"; private static final String x86_EMULATOR_RELATIVE_PATH = "/tools/emulator-x86"; private static final String EMULATOR_RELATIVE_PATH = "/tools/emulator"; private static final String EMULATOR_VM_PARAMETER = "-avd"; private static String selectedEmulatorPath = ""; public void execute(final IAndroidLogicInstance instance, int timeout, IProgressMonitor monitor) throws InstanceStartException, StartTimeoutException, StartCancelledException { long timeoutLimit = AndroidLogicUtils.getTimeoutLimit(timeout); info("Starting the Android Emulator process: " + instance); instance.setWindowHandle(0); File userData = instance.getUserdata(); if (userData != null) { File userdataDir = userData.getParentFile(); if ((userdataDir != null) && (!userdataDir.exists())) { userdataDir.mkdirs(); } } selectedEmulatorPath = retrieveEmulatorExecutableName(instance); File emulatorExe = new File(SdkUtils.getSdkPath(), selectedEmulatorPath); List<String> cmdList = new LinkedList<String>(); cmdList.add(emulatorExe.getAbsolutePath()); cmdList.add(EMULATOR_VM_PARAMETER); cmdList.add(instance.getName()); Properties propArgs = instance.getCommandLineArgumentsAsProperties(); IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); String adtEmuOptions = store.getString(AdtPrefs.PREFS_EMU_OPTIONS); StringTokenizer adtOptionsTokenizer = new StringTokenizer(adtEmuOptions, " "); while (adtOptionsTokenizer.hasMoreTokens()) { String nextToken = adtOptionsTokenizer.nextToken(); cmdList.add(nextToken); } for (Object key : propArgs.keySet()) { String value = propArgs.getProperty(key.toString()); if (key.equals("other")) { StringTokenizer stringTokenizer = new StringTokenizer(value, " "); while (stringTokenizer.hasMoreTokens()) { cmdList.add(stringTokenizer.nextToken()); } } else { if ((value.trim().length() > 0) && !value.equals(Boolean.TRUE.toString())) { cmdList.add(key.toString()); if (Platform.getOS().equals(Platform.OS_MACOSX)) { if (value.contains(" ")) { value = "\"" + value + "\""; } } else { if (value.contains("\\")) { value = value.replace("\\", "\\\\"); } if (value.contains(" ")) { value = value.replace(" ", "\\ "); } } cmdList.add(value); } else if ((value.trim().length() > 0) && value.equals(Boolean.TRUE.toString())) { cmdList.add(key.toString()); } } } // add proxy in case it is needed Properties properties = instance.getProperties(); if (properties != null) { String useProxy = properties.getProperty(IDevicePropertiesConstants.useProxy, IDevicePropertiesConstants.defaultUseProxyValue); if (Boolean.TRUE.toString().equals(useProxy)) { addEmulatorProxyParameter(cmdList); } } StringBuffer cmdLog = new StringBuffer(""); boolean httpProxyParamFound = false; boolean logHttpProxyUsage = false; for (String param : cmdList) { // Do not log -http-proxy information if (!httpProxyParamFound) { if (!param.equals(EMULATOR_HTTP_PROXY_PARAMETER)) { if (param.startsWith(emulatorExe.getAbsolutePath())) { // do not log emulator full path cmdLog.append(selectedEmulatorPath + " "); } else { cmdLog.append(param + " "); } } else { httpProxyParamFound = true; logHttpProxyUsage = true; } } else { httpProxyParamFound = false; } } // Append http proxy usage to log if (logHttpProxyUsage) { cmdLog.append("\nProxy settings are being used by the started emulator (-http-proxy parameter)."); } // add command to not start from snapshot if (properties != null) { String startFromSnapshot = properties.getProperty(IDevicePropertiesConstants.startFromSnapshot, IDevicePropertiesConstants.defaultstartFromSnapshotValue); if (Boolean.FALSE.toString().equals(startFromSnapshot)) { cmdList.add(EMULATOR_NO_SNAPSHOT_LOAD); } } // Add the command to not save snapshot if (properties != null) { String saveSnapshot = properties.getProperty(IDevicePropertiesConstants.saveSnapshot, IDevicePropertiesConstants.defaulSaveSnapshot); if (Boolean.FALSE.toString().equals(saveSnapshot)) { cmdList.add(EMULATOR_NO_SNAPSHOT_SAVE); } } Process p; try { p = AndroidLogicUtils.executeProcess(cmdList.toArray(new String[0]), cmdLog.toString()); } catch (AndroidException e) { throw new InstanceStartException(e); } info("Wait until and emulator with the VM " + instance.getName() + " is up "); AndroidLogicUtils.testProcessStatus(p); instance.setProcess(p); instance.setComposite(null); final String avdName = instance.getName(); if (!Platform.getOS().equals(Platform.OS_MACOSX)) { Collection<IViewPart> openedAndroidViews = EclipseUtils.getAllOpenedViewsWithId(EMULATOR_VIEW); if (!openedAndroidViews.isEmpty()) { Runnable runnable = new Runnable() { public void run() { long windowHandle = -1; long timeOutToFindWindow = System.currentTimeMillis() + 30000; do { try { Thread.sleep(250); } catch (InterruptedException e) { // do nothing } try { AndroidLogicUtils.testTimeout(timeOutToFindWindow, ""); } catch (StartTimeoutException e) { debug("Emulator window could not be found, instance :" + avdName); break; } try { int port = AndroidLogicUtils.getEmulatorPort(DDMSFacade .getSerialNumberByName(instance.getName())); if (port > 0) { windowHandle = NativeUIUtils.getWindowHandle(instance.getName(), port); } } catch (Exception t) { t.getCause().getMessage(); System.out.println(t.getCause().getMessage()); } } while (windowHandle <= 0); if (windowHandle > 0) { instance.setWindowHandle(windowHandle); NativeUIUtils.hideWindow(windowHandle); } } }; Thread getHandleThread = new Thread(runnable, "Window Handle Thread"); getHandleThread.start(); } } if (instance.getProperties() .getProperty(IDevicePropertiesOSConstants.useVnc, NativeUIUtils.getDefaultUseVnc()) .equals(Boolean.TRUE.toString())) { do { try { Thread.sleep(450); } catch (InterruptedException e) { // do nothing } AndroidLogicUtils.testCanceled(monitor); try { AndroidLogicUtils.testTimeout(timeoutLimit, NLS.bind(EmulatorNLS.EXC_TimeoutWhileStarting, avdName)); } catch (StartTimeoutException e) { debug("Emulator start timeout has been reached, instance :" + avdName + " has device: " + instance.hasDevice() + "isOnline? " + DDMSFacade.isDeviceOnline(DDMSFacade.getSerialNumberByName(avdName))); throw e; } } while (!isEmulatorReady(avdName)); } Thread t = new Thread("Process Error") { @Override public void run() { boolean shouldTryAgain = true; for (int i = 0; (i < 90) && shouldTryAgain; i++) { try { sleep(500); Process p = instance.getProcess(); if (p != null) { AndroidLogicUtils.testProcessStatus(p); } } catch (Exception e) { StudioLogger.info(StartEmulatorProcessLogic.class, "Trying to stop the emulator process: execution stopped too early"); DialogWithToggleUtils.showError( EmulatorPlugin.EMULATOR_UNEXPECTEDLY_STOPPED, EmulatorNLS.GEN_Error, NLS.bind( EmulatorNLS.ERR_AndroidLogicPlugin_EmulatorStopped, instance.getName())); shouldTryAgain = false; try { instance.stop(true); } catch (InstanceStopException ise) { StudioLogger.error(StartEmulatorProcessLogic.class, "Error trying to stop instance on process error", ise); } } } } }; t.start(); debug("Emulator instance is now up and running... " + instance); } /** * retrives the emulator executable name according to abi type property * * @param instance * @return */ private static String retrieveEmulatorExecutableName(IAndroidLogicInstance instance) { String emulatorPath = null; Properties prop = instance.getProperties(); String abiType = prop.getProperty("Abi_Type"); if ((abiType == null) || (abiType.equals(""))) { emulatorPath = EMULATOR_RELATIVE_PATH; } else if (abiType.toLowerCase().contains("arm")) { emulatorPath = ARM_EMULATOR_RELATIVE_PATH; } else { emulatorPath = x86_EMULATOR_RELATIVE_PATH; } File emulatorExe = new File(SdkUtils.getSdkPath(), emulatorPath + ".exe"); if (!emulatorExe.exists()) { emulatorPath = EMULATOR_RELATIVE_PATH; } return emulatorPath; } /** * Retrieve the Proxy service. * * @return IProxyService instance. */ @SuppressWarnings({ "rawtypes", "unchecked" }) private IProxyService retrieveProxyService() { IProxyService proxyService = null; ServiceReference service = EmulatorPlugin.getDefault().getBundle().getBundleContext() .getServiceReference(IProxyService.class.getCanonicalName()); if (service != null) { proxyService = (IProxyService) EmulatorPlugin.getDefault().getBundle().getBundleContext() .getService(service); } return proxyService; } /** * Add the http-proxy parameter to the emulator command line. * * @param cmdList * List holding the commands to be called. */ private void addEmulatorProxyParameter(List<String> cmdList) { IProxyService proxyService = retrieveProxyService(); if (proxyService != null) { String host = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE).getHost(); int port = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE).getPort(); boolean isAuthenticationRequired = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE) .isRequiresAuthentication(); String userId = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE).getUserId(); String password = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE).getPassword(); // there must be a host in order to access via proxy if (host != null) { cmdList.add(EMULATOR_HTTP_PROXY_PARAMETER); // add proxy info to the command list - authentication needed if (isAuthenticationRequired) { cmdList.add(PROXY_HTTP + userId + PROXY_COLON + password + PROXY_AT + host + PROXY_COLON + Integer.valueOf(port).toString()); } // add proxy info to the command list - no authentication needed else { cmdList.add(PROXY_HTTP + host + PROXY_COLON + Integer.valueOf(port).toString()); } } } } private boolean isEmulatorReady(String avdName) { String serialNum = DDMSFacade.getSerialNumberByName(avdName); return DDMSFacade.isDeviceOnline(serialNum); } }