/* * 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.device.instance; import static com.motorola.studio.android.common.log.StudioLogger.error; import static com.motorola.studio.android.common.log.StudioLogger.info; import static com.motorola.studio.android.common.log.StudioLogger.warn; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.sequoyah.device.framework.model.AbstractMobileInstance; import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler; import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate; import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.model.IWorkbenchAdapter; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.internal.avd.AvdInfo; import com.motorola.studio.android.adt.DDMSFacade; import com.motorola.studio.android.adt.ISerialNumbered; import com.motorola.studio.android.adt.SdkUtils; import com.motorola.studio.android.emulator.EmulatorPlugin; import com.motorola.studio.android.emulator.core.exception.InstanceStopException; import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; import com.motorola.studio.android.emulator.core.model.IInputLogic; import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; import com.motorola.studio.android.emulator.device.definition.AndroidEmuDefMgr; import com.motorola.studio.android.emulator.device.instance.options.StartupOptionsMgt; import com.motorola.studio.android.emulator.i18n.EmulatorNLS; import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic; import com.motorola.studio.android.emulator.logic.AndroidLogicUtils; import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; import com.motorola.studio.android.emulator.logic.stop.AndroidEmulatorStopper; import com.motorola.studio.android.nativeos.NativeUIUtils; /** * DESCRIPTION: * This class represents a Android Emulator instance * * RESPONSIBILITY: * - Hold all attributes of an Android Emulator instance * - Provide methods for testing if started and to stop the instance * * COLABORATORS: * None. * * USAGE: * This class is not meant to be used directly by the user. All commands to the * Android Emulator instance shall be provided through the device framework. */ public class AndroidDeviceInstance extends AbstractMobileInstance implements IAndroidLogicInstance, IWorkbenchAdapter, ISerialNumbered { /** * The protocol that is executed by this instance */ private ProtocolHandle handle; /** * True if this instance has and supports CLI display; false otherwise */ private boolean hasCli = false; /** * Current layout of this instance */ private String currentLayout; private Process process; private long windowHandle; private Composite composite; /** * Tests if this instance is in started or stopped state * * @return true if started, false if stopped */ public boolean isStarted() { boolean instanceStarted = false; String status = getStatus(); if (EmulatorPlugin.STATUS_ONLINE_ID.equals(status)) { instanceStarted = true; } return instanceStarted; } public boolean isConnected() { if (super.getProperties() .getProperty(IDevicePropertiesConstants.useVnc, NativeUIUtils.getDefaultUseVnc()) .equals("true")) { ProtocolHandle protocolHandle = getProtocolHandle(); if (protocolHandle != null) { return PluginProtocolActionDelegate.isProtocolRunning(protocolHandle); } return false; } else { return isStarted(); } } /** * Method used by the emulator core to stop the instance on errors * * @see IAndroidEmulatorInstance#stop(boolean) * * @param force True if no interaction with the user is desired to * perform the stop operation; false otherwise */ public void stop(boolean force) throws InstanceStopException { info("Stopping the Android Emulator instance: " + this); // FIRST SCENARIO: THE INSTANCE IS STARTED AND FAILED DURING OPERATION // If the instance is in started state, the stop handler is called to delegate the state // maintenance to TmL, which is correct. if (isStarted()) { if (getStateMachineHandler().isTransitioning()) { // Free other threads to continue their jobs if one is already running the procedure info("The instance is already executing a stop process: " + this); throw new InstanceStopException("The instance is already executing a stop process"); } else { info("Instance is started. Run the regular transition to stopped status: " + this); ServiceHandler stopHandler = EmulatorPlugin.getStopServiceHandler(); if (stopHandler != null) { try { Map<Object, Object> args = new HashMap<Object, Object>(); args.put(EmulatorPlugin.FORCE_ATTR, true); stopHandler.run(this, args); } catch (Exception e) { // Should not enter here, because the state has been tested before error("The instance is not in an appropriate state for stopping"); } info("Finished stop process: " + this); } } } // SECOND SCENARIO: THE INSTANCE WAS STARTING AND FAILED DURING THE PROCESS // If the instance is not in started state, TmL will not allow the stop service to run. In // this case, the methods must be called manually, and the state does not need maintenance // (it has never been updated, as updating happens in the end of a transition) else { if (getStateMachineHandler().isTransitioning()) { info("Instance is not fully started yet. Execute a stop process directly..." + this); final Job haltJob = new Job(EmulatorNLS.UI_AndroidDeviceInstance_StopInstanceJob) { @Override protected IStatus run(IProgressMonitor monitor) { AndroidEmulatorStopper.stopInstance(AndroidDeviceInstance.this, true, true, monitor); if (getStatus().equals(EmulatorPlugin.STATUS_OFFLINE_NO_DATA)) { info("Instance was initially in stopped/clean status. Rollback if needed." + this); File userdataFile = getUserdata(); if ((userdataFile != null) && userdataFile.exists()) { info("Deleted data created during the start tentative." + userdataFile); userdataFile.delete(); } } info("Finished stop process: " + AndroidDeviceInstance.this); return Status.OK_STATUS; } }; haltJob.schedule(); } } } /** * */ @Override public Properties getProperties() { Properties properties = super.getProperties(); // synchronize instance properties data with current sdk... File fromSdk = SdkUtils.getUserdataDir(getName()); if (fromSdk != null) { properties.put(IDevicePropertiesConstants.vmPath, fromSdk.getAbsolutePath()); } return properties; } /** * @see IAndroidEmulatorInstance#getHasCli() */ public boolean getHasCli() { return hasCli; } /** * @see IAndroidEmulatorInstance#getInstanceIdentifier() */ public String getInstanceIdentifier() { return getSerialNumber(); } /** * @see IAndroidEmulatorInstance#getProtocolHandle() */ public ProtocolHandle getProtocolHandle() { return handle; } /** * @see IAndroidEmulatorInstance#getEmulatorDefId() */ public String getEmulatorDefId() { return getProperties().getProperty(IDevicePropertiesConstants.emulatorDefId); } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#getCurrentLayout() */ public String getCurrentLayout() { return currentLayout; } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#setCurrentLayout(java.lang.String) */ public void setCurrentLayout(String layoutName) { currentLayout = layoutName; } /** * @see IAndroidEmulatorInstance#setHasCli(boolean) */ public void setHasCli(boolean hasCli) { this.hasCli = hasCli; } /** * @see IAndroidEmulatorInstance#setProtocolHandle(ProtocolHandle) */ public void setProtocolHandle(ProtocolHandle handle) { this.handle = handle; } /** * Resets all the previous runtime state to clean them for next execution */ void resetRuntimeVariables() { handle = null; hasCli = false; currentLayout = null; } /** * This version of getAdapter needs to assure that only an Android * Device instance is compatible with itself * * @see IAdaptable#getAdapter(Class) */ @SuppressWarnings("rawtypes") @Override public Object getAdapter(Class adapter) { if (adapter.isInstance(this)) { return this; } else { return null; } } /** * Retrieves the default properties to be used upon instance creation * * @param defaultProperties property object to be filled * @param vmSkin Android VM skin */ public static void populateWithDefaultProperties(Properties defaultProperties) { AndroidEmuDefMgr emuDefMgr = AndroidEmuDefMgr.getInstance(); // When removing the emulator definition extension, remove this hardcoded set as // well as the constant declaration from the Activator. If the Mot skin plugin is used, // find another way to set this variable String emuDefId = EmulatorPlugin.DEFAULT_EMULATOR_DEFINITION; // Emulator Definition defaultProperties.setProperty(IDevicePropertiesConstants.emulatorDefId, emuDefId); // skin from Emulator Definition defaultProperties.setProperty(IDevicePropertiesConstants.skinId, emuDefMgr.getSkinId(emuDefId)); // command line arguments from Emulator Definition defaultProperties.setProperty(IDevicePropertiesConstants.commandline, emuDefMgr.getCommandLineArgumentsForEmuDefinition(emuDefId)); // default timeout defaultProperties.setProperty(IDevicePropertiesConstants.timeout, IDevicePropertiesConstants.defaultTimeoutValue); //default useVnc defaultProperties.setProperty(IDevicePropertiesConstants.useVnc, NativeUIUtils.getDefaultUseVnc()); //default useProxy defaultProperties.setProperty(IDevicePropertiesConstants.useProxy, IDevicePropertiesConstants.defaultUseProxyValue); } /** * Populate VM Target and VM skin * * @param instanceName * @param instanceProperties */ public static void populateWithVMInfo(String instanceName, Properties instanceProperties) { AvdInfo vmInfo = SdkUtils.getValidVm(instanceName); if (vmInfo != null) { // VM target instanceProperties.setProperty(IDevicePropertiesConstants.vmTarget, vmInfo.getTarget() .getName()); // ABI Type instanceProperties.setProperty(IDevicePropertiesConstants.abiType, vmInfo.getAbiType()); // VM skin instanceProperties.setProperty(IDevicePropertiesConstants.vmSkin, SdkUtils.getSkin(vmInfo)); // VM path instanceProperties.setProperty(IDevicePropertiesConstants.vmPath, vmInfo.getDataFolderPath()); String useSnapShot = vmInfo.getProperties().get("snapshot.present"); if (useSnapShot == null) { useSnapShot = "false"; } instanceProperties.setProperty(IDevicePropertiesConstants.useSnapshots, useSnapShot); if (instanceProperties.getProperty(IDevicePropertiesConstants.startFromSnapshot) == null) { instanceProperties.setProperty(IDevicePropertiesConstants.startFromSnapshot, useSnapShot); } if (instanceProperties.getProperty(IDevicePropertiesConstants.saveSnapshot) == null) { instanceProperties .setProperty(IDevicePropertiesConstants.saveSnapshot, useSnapShot); } } } public String getSkinId() { return getProperties().getProperty(IDevicePropertiesConstants.skinId); } @SuppressWarnings("restriction") public File getSkinPath() { File skinFile = null; AvdInfo avdInfo = SdkUtils.getValidVm(getName()); if (avdInfo != null) { String skinPath = avdInfo.getProperties().get("skin.path"); skinPath = SdkUtils.getCurrentSdk().getSdkLocation() + skinPath; IAndroidTarget target = avdInfo.getTarget(); File candidateFile = new File(skinPath); //If path specified on the skin does not exist, try to retrieve it from the target. if (!candidateFile.exists()) { candidateFile = SdkUtils.getCurrentSdk().getAvdManager() .getSkinPath(SdkUtils.getSkin(avdInfo), target); } if (!target.isPlatform()) { if (!candidateFile.isDirectory()) { IAndroidTarget baseTarget = target.getParent(); skinPath = getSkinFolderPath(baseTarget); skinFile = new File(skinPath); } else { skinFile = candidateFile; } } else { skinFile = candidateFile; } } return skinFile; } private String getSkinFolderPath(IAndroidTarget target) { String vmSkin = getProperties().getProperty(IDevicePropertiesConstants.vmSkin); return target.getLocation() + File.separator + "skins" + File.separator + vmSkin; } /** * Get the timeout in milliseconds * * @see com.motorola.studio.android.emulator.logic.IAndroidLogicInstance#getTimeout() */ public int getTimeout() { int timeout = 0; String timeoutString = null; try { info("Try to get Timeout property from " + this); Properties instanceProps = getProperties(); timeoutString = instanceProps.getProperty(IDevicePropertiesConstants.timeout); timeout = Integer.parseInt(timeoutString) * 1000; //convert to milis } catch (Exception e) { warn("Unnable to parse timeout string:" + timeoutString); timeout = Integer.parseInt(IDevicePropertiesConstants.defaultTimeoutValue) * 1000; } return timeout; } public IAndroidTarget getAndroidTarget() { IAndroidTarget result = null; AvdInfo avdInfo = SdkUtils.getValidVm(getName()); if (avdInfo != null) { result = avdInfo.getTarget(); } return result; } public String getTarget() { String result = null; IAndroidTarget target = getAndroidTarget(); if (target != null) { result = target.getName(); } return result; } public int getAPILevel() { int result = -1; IAndroidTarget target = getAndroidTarget(); if (target != null) { result = target.getVersion().getApiLevel(); } return result; } public String getCommandLineArguments() { return getProperties().getProperty(IDevicePropertiesConstants.commandline, NativeUIUtils.getDefaultCommandLine()); } public AbstractStartAndroidEmulatorLogic getStartLogic() { String emuDefinition = getEmulatorDefId(); AndroidEmuDefMgr definitionManager = AndroidEmuDefMgr.getInstance(); return definitionManager.getStartLogic(emuDefinition); } public IInputLogic getInputLogic() { String emuDefinition = getEmulatorDefId(); AndroidEmuDefMgr definitionManager = AndroidEmuDefMgr.getInstance(); return definitionManager.getInputLogic(emuDefinition, this); } public boolean hasDevice() { return (DDMSFacade.getDeviceBySerialNumber(getSerialNumber()) != null); } public void changeOrientation(final String parameters) { new Thread(new Runnable() { public void run() { try { DDMSFacade.execRemoteApp(getSerialNumber(), AndroidLogicUtils.ORIENTATION_BASE_COMMAND + parameters, new NullProgressMonitor()); } catch (IOException e) { error("Failed to send the command to change the emulator display orientation to portrait."); } } }).start(); } /* (non-Javadoc) * @see com.motorola.studio.android.emulator.logic.IAndroidLogicInstance#getUserdata() */ public File getUserdata() { return SdkUtils.getUserdataFile(getName()); } /* (non-Javadoc) * @see com.motorola.studio.android.emulator.logic.IAndroidLogicInstance#getSnapshotOriginalFilePath() */ public File getSnapshotOriginalFilePath() { String snapshotFilePath; File snapshotOriginalFile = null; snapshotFilePath = SdkUtils.getSdkToolsPath() + "lib" + File.separator + "emulator" + File.separator + "snapshots.img"; snapshotOriginalFile = new File(snapshotFilePath); if (!snapshotOriginalFile.exists()) { snapshotOriginalFile = null; } return snapshotOriginalFile; } /* (non-Javadoc) * @see com.motorola.studio.android.emulator.logic.IAndroidLogicInstance#getStateData() */ public List<File> getStateData() { return SdkUtils.getStateDataFiles(getName()); } /* (non-Javadoc) * @see com.motorola.studio.android.emulator.logic.IAndroidLogicInstance#isClean() */ public boolean isClean() { boolean userdataExists = false; File userdataFile = getUserdata(); if ((userdataFile != null) && (userdataFile.exists())) { userdataExists = true; } return !userdataExists; } @Override public String toString() { // Do not use getInstanceIdentifier method here (it is used by several // logs - high exposure - and leads to synchronized methods that may // cause deadlocks). return getName(); } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#getProcess() */ public Process getProcess() { return process; } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#setProcess(java.lang.Process) */ public void setProcess(Process process) { this.process = process; } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#setWindowHandle(int) */ public long getWindowHandle() { return windowHandle; } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#getWindowHandle() */ public void setWindowHandle(long handle) { windowHandle = handle; } /* * (non-Javadoc) * @see com.motorola.studio.android.adt.ISerialNumbered#getDeviceName() */ public String getDeviceName() { return getName(); } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#getFullName() */ public String getFullName() { String suffix = getNameSuffix(); if (suffix != null) { return getName() + " (" + suffix + ")"; } else { return getName(); } } /* * (non-Javadoc) * @see org.eclipse.ui.model.IWorkbenchAdapter#getChildren(java.lang.Object) */ public Object[] getChildren(Object o) { return new Object[0]; } /* * (non-Javadoc) * @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object) */ public ImageDescriptor getImageDescriptor(Object object) { return null; } /* * (non-Javadoc) * @see org.eclipse.ui.model.IWorkbenchAdapter#getLabel(java.lang.Object) */ public String getLabel(Object o) { return getName(); } /* * (non-Javadoc) * @see org.eclipse.ui.model.IWorkbenchAdapter#getParent(java.lang.Object) */ public Object getParent(Object o) { return null; } /* * (non-Javadoc) * @see com.motorola.studio.android.adt.ISerialNumbered#getSerialNumber() */ public String getSerialNumber() { return DDMSFacade.getSerialNumberByName(getName()); } /* * (non-Javadoc) * @see com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance#isAvailable() */ public boolean isAvailable() { return !getStatus().equals(EmulatorPlugin.STATUS_NOT_AVAILABLE); } /* (non-Javadoc) * @see com.motorola.studio.android.emulator.logic.IAndroidLogicInstance#getCommandLineArgumentsAsProperties() */ public Properties getCommandLineArgumentsAsProperties() { return StartupOptionsMgt.parseCommandLine(getCommandLineArguments()); } public Composite getComposite() { return composite; } public void setComposite(Composite composite) { this.composite = composite; } }