diff options
Diffstat (limited to 'src/plugins/emulator/src/com')
121 files changed, 27596 insertions, 0 deletions
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/EmulatorPlugin.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/EmulatorPlugin.java new file mode 100644 index 0000000..eaf7512 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/EmulatorPlugin.java @@ -0,0 +1,456 @@ +/* +* 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; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.List; +import java.util.Properties; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.osgi.util.NLS; +import org.eclipse.sequoyah.device.common.utilities.BasePlugin; +import org.eclipse.sequoyah.device.framework.events.IInstanceListener; +import org.eclipse.sequoyah.device.framework.events.InstanceAdapter; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent.InstanceEventType; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.sequoyah.device.framework.factory.DeviceTypeRegistry; +import org.eclipse.sequoyah.device.framework.model.IDeviceType; +import org.eclipse.sequoyah.device.framework.model.IService; +import org.eclipse.sequoyah.device.framework.model.handler.IServiceHandler; +import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler; +import org.eclipse.sequoyah.device.framework.ui.DeviceUIPlugin; +import org.eclipse.sequoyah.device.framework.ui.view.InstanceMgtView; +import org.eclipse.sequoyah.device.framework.ui.wizard.DefaultDeviceTypeMenuWizardPage; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +import com.motorola.studio.android.AndroidPlugin; +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.adt.DdmsRunnable; +import com.motorola.studio.android.adt.StudioAndroidEventManager; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.common.preferences.DialogWithToggleUtils; +import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.device.AndroidDeviceUtils; +import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; +import com.motorola.studio.android.emulator.device.SequoyahLogRedirector; +import com.motorola.studio.android.emulator.device.instance.AndroidDevInstListener; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.device.refresh.InstancesListRefresh; +import com.motorola.studio.android.emulator.device.sync.DeviceViewsSync; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; + +/** + * The activator class controls the plug-in life cycle + */ +public class EmulatorPlugin extends AbstractUIPlugin +{ + // The plug-in ID + public static final String PLUGIN_ID = "com.motorola.studio.android.emulator"; + + // The shared instance + private static EmulatorPlugin plugin; + + // The ID of the device declared by this plug-in + public static final String DEVICE_ID = PLUGIN_ID + ".androidDevice"; + + // The ID of all the status declared by this plug-in + public static final String STATUS_ONLINE_ID = PLUGIN_ID + ".status.online"; + + public static final String STATUS_OFFLINE_NO_DATA = PLUGIN_ID + ".status.offlineNoData"; + + public static final String STATUS_OFFLINE = PLUGIN_ID + ".status.offline"; + + public static final String STATUS_NOT_AVAILABLE = PLUGIN_ID + ".status.notavailable"; + + public static final String SERVICE_INIT_ID = PLUGIN_ID + ".initEmulatorService"; + + public static final String STOP_SERVICE_ID = PLUGIN_ID + ".stopService"; + + public static final String START_SERVICE_ID = PLUGIN_ID + ".startService"; + + private static final String DEV_MANAGER_HELP = DeviceUIPlugin.PLUGIN_ID + ".devmgr"; + + private static final String NEW_DEVICE_HELP = DeviceUIPlugin.PLUGIN_ID + ".newdev"; + + /** + * Reference the id of the extension point with the default Android Emulator definitions... + */ + public static String DEFAULT_EMULATOR_DEFINITION = + "com.motorola.studio.android.emulator10.defaultEmulatorDefinitions"; + + public static final String FORCE_ATTR = "force"; + + public static final String EMULATOR_UNEXPECTEDLY_STOPPED = "emulator.unexpectedly.stopped"; + + private static AndroidDevInstListener instanceListener; + + private static DdmsRunnable connectedListener = new DdmsRunnable() + { + @Override + public void run(String serialNumber) + { + if (DDMSFacade.isEmulator(serialNumber)) + { + InstancesListRefresh.refresh(); + + info("New Device connected at " + serialNumber); + + String vmName = DDMSFacade.getNameBySerialNumber(serialNumber); + + if (vmName != null) + { + DeviceFrameworkManager devFrameworkManager = + DeviceFrameworkManager.getInstance(); + + IAndroidEmulatorInstance instance = + devFrameworkManager.getInstanceByName(vmName); + + if (instance instanceof AndroidDeviceInstance) + { + final AndroidDeviceInstance emulatorInstance = + (AndroidDeviceInstance) instance; + + AndroidDeviceUtils.fireDummyStartTransition(emulatorInstance, serialNumber); + + } + } + } + } + }; + + private static DdmsRunnable disconnectedListener = new DdmsRunnable() + { + @Override + public void run(String serialNum) + { + if (DDMSFacade.isEmulator(serialNum)) + { + info("Device just disconnected from serial=" + serialNum); + + String vmName = DDMSFacade.getNameBySerialNumber(serialNum); + + if (vmName != null) + { + IAndroidEmulatorInstance instance = + DeviceFrameworkManager.getInstance().getInstanceByName(vmName); + + if ((instance != null) && (instance.isStarted())) + { + try + { + instance.stop(true); + DialogWithToggleUtils.showError(EMULATOR_UNEXPECTEDLY_STOPPED, + EmulatorNLS.GEN_Error, NLS.bind( + EmulatorNLS.ERR_AndroidLogicPlugin_EmulatorStopped, + instance.getName())); + + } + catch (Exception e) + { + error("Error trying to force the stop process on instance associated to disconnected device: " + + instance); + } + } + + if (instance instanceof AndroidDeviceInstance) + { + ((AndroidDeviceInstance) instance).setNameSuffix(null); + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, + (AndroidDeviceInstance) instance)); + } + } + else + { + // This block is executed if we get a vmName == null condition. This can happen if + // ADT updates the device in a way that it makes the name not accessible. + // + // What is needed to be done in such a case is to iterate on all TmL instances, looking for + // objects that contain serialNumber as the instance suffix. This guarantees that we will not + // leave a not consistent serial number being displayed at the Instance Management view. + + for (IAndroidEmulatorInstance instance : DeviceFrameworkManager.getInstance() + .getAllInstances()) + { + if (instance instanceof AndroidDeviceInstance) + { + AndroidDeviceInstance androidInstance = + (AndroidDeviceInstance) instance; + String instanceSuffix = androidInstance.getNameSuffix(); + + if ((instanceSuffix != null) && instanceSuffix.equals(serialNum)) + { + androidInstance.setNameSuffix(null); + + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, + androidInstance)); + } + } + } + } + } + } + }; + + private static final Runnable sdkLoaderListener = new Runnable() + { + @Override + public void run() + { + InstancesListRefresh.refresh(); + if (!Platform.getOS().equals(Platform.OS_MACOSX)) + { + IPreferenceStore store = getDefault().getPreferenceStore(); + boolean deviceStartupOptionsUpdated = + store.getBoolean("DeviceStartupOptionsUpdated"); + if (!deviceStartupOptionsUpdated) + { + for (IAndroidEmulatorInstance instance : DeviceFrameworkManager.getInstance() + .getAllInstances()) + { + if (instance instanceof AndroidDeviceInstance) + { + AndroidDeviceInstance androidInstance = + (AndroidDeviceInstance) instance; + + Properties emuProperties = androidInstance.getProperties(); + + String commandline = + emuProperties.getProperty( + IDevicePropertiesConstants.commandline, ""); + if (commandline.contains("-no-window")) + { + commandline = commandline.replace("-no-window", ""); + } + emuProperties.setProperty(IDevicePropertiesConstants.commandline, + commandline); + androidInstance.setProperties(emuProperties); + + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, + androidInstance)); + } + } + store.setValue("DeviceStartupOptionsUpdated", true); + } + } + } + }; + + private static IInstanceListener sequoyahInstanceListener = new InstanceAdapter() + { + @Override + public void instanceUpdated(InstanceEvent e) + { + AbstractAndroidView.updateInstanceName(e.getInstance()); + } + }; + + private static ServiceHandler stopServiceHandler = null; + + private static ServiceHandler startServiceHandler = null; + + private static String stopServiceId = null; + + private static String startServiceId = null; + + /** + * The constructor + */ + public EmulatorPlugin() + { + plugin = this; + } + + /** + * Activates the plug-in and initializes the logger + * + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + @Override + public void start(BundleContext context) throws Exception + { + StudioLogger.debug(EmulatorPlugin.class, "Starting MOTODEV Android Emulator Plugin..."); + + super.start(context); + + start(); + + StudioLogger.debug(EmulatorPlugin.class, "MOTODEV Android Emulator Plugin started."); + } + + private void start() + { + // Setting the TmL logger to redirect logs to the logger controlled + // by this class + SequoyahLogRedirector tmlLogger = new SequoyahLogRedirector(); + org.eclipse.sequoyah.vnc.utilities.logger.Logger.setLogger(tmlLogger); + BasePlugin.getBaseDefault().setLogger(tmlLogger); + + instanceListener = new AndroidDevInstListener(); + InstanceEventManager.getInstance().addInstanceListener(instanceListener); + StudioAndroidEventManager.asyncAddDeviceChangeListeners(connectedListener, + disconnectedListener); + + AndroidPlugin.getDefault().addSDKLoaderListener(sdkLoaderListener); + // Emulator Views synchronization + DeviceViewsSync.getInstance().initialize(); + // Setting context sensitive help IDs for the TmL screens we use + DefaultDeviceTypeMenuWizardPage.setHelpContextId(NEW_DEVICE_HELP); + InstanceMgtView.setHelp(DEV_MANAGER_HELP); + InstanceEventManager.getInstance().addInstanceListener(sequoyahInstanceListener); + registerStopServiceId(STOP_SERVICE_ID); + registerStartServiceId(START_SERVICE_ID); + } + + /** + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + @Override + public void stop(BundleContext context) throws Exception + { + AndroidPlugin.getDefault().removeSDKLoaderListener(sdkLoaderListener); + InstanceEventManager.getInstance().removeInstanceListener(instanceListener); + StudioAndroidEventManager.asyncRemoveDeviceChangeListeners(connectedListener, + disconnectedListener); + InstanceEventManager.getInstance().removeInstanceListener(sequoyahInstanceListener); + unregisterStopServiceHandler(); + unregisterStartServiceHandler(); + plugin = null; + super.stop(context); + } + + /** + * Registers a stop service id, through which the stop service handler will be found and + * used to delegate stop action of the instances + * if possible + * + * @param stopServiceId The stop service id to be registered + */ + public static void registerStopServiceId(String stopServiceId) + { + EmulatorPlugin.stopServiceId = stopServiceId; + } + + /** + * Unregisters the current stop service handler and stop service id. + * + * After this method is called, it will not be possible for the instance class to delegate the + * stop action to a handler. + */ + public static void unregisterStopServiceHandler() + { + stopServiceHandler = null; + stopServiceId = null; + } + + /** + * Retrieves the stop service handler. + * + * @return The currently registered stop service handler, or <null> if no handler is registered. + */ + public static ServiceHandler getStopServiceHandler() + { + if ((stopServiceHandler == null) && (stopServiceId != null)) + { + // find the appropriate stop service handler + IDeviceType device = + DeviceTypeRegistry.getInstance().getDeviceTypeById(EmulatorPlugin.DEVICE_ID); + List<IService> services = device.getServices(); + for (IService service : services) + { + IServiceHandler handler = service.getHandler(); + if (handler.getService().getId().equals(stopServiceId)) + { + stopServiceHandler = (ServiceHandler) handler; + break; + } + } + } + + return stopServiceHandler; + } + + /** + * Registers a start service id, through which the stop service handler will be found and + * used to delegate start action of the instances + * if possible + * + * @param stopServiceId The stop service id to be registered + */ + public static void registerStartServiceId(String startServiceId) + { + EmulatorPlugin.startServiceId = startServiceId; + } + + /** + * Unregisters the current start service handler and stop service id. + * + * After this method is called, it will not be possible for the instance class to delegate the + * start action to a handler. + */ + public static void unregisterStartServiceHandler() + { + startServiceHandler = null; + startServiceId = null; + } + + /** + * Retrieves the start service handler. + * + * @return The currently registered start service handler, or <null> if no handler is registered. + */ + public static ServiceHandler getStartServiceHandler() + { + if ((startServiceHandler == null) && (startServiceId != null)) + { + // find the appropriate stop service handler + IDeviceType device = + DeviceTypeRegistry.getInstance().getDeviceTypeById(EmulatorPlugin.DEVICE_ID); + List<IService> services = device.getServices(); + for (IService service : services) + { + IServiceHandler handler = service.getHandler(); + if (handler.getService().getId().equals(startServiceId)) + { + startServiceHandler = (ServiceHandler) handler; + break; + } + } + } + + return startServiceHandler; + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static EmulatorPlugin getDefault() + { + return plugin; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/devfrm/DeviceFrameworkManager.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/devfrm/DeviceFrameworkManager.java new file mode 100644 index 0000000..401d3ff --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/devfrm/DeviceFrameworkManager.java @@ -0,0 +1,315 @@ +/* +* 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.core.devfrm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.Platform; + +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; + +/** + * DESCRIPTION: + * This class manages the device frameworks that extend the deviceFramework + * extension + * + * RESPONSIBILITY: + * Retrieve all deviceFramework extension data and provide a compiled + * view of the information provided by each extension implementer + * + * COLABORATORS: + * None. + * + * USAGE: + * Use each public method to get the desired information + */ +public class DeviceFrameworkManager +{ + /* + * Extension point related ids section + */ + private static final String DEV_FRAMEWORK_EXTENSION_POINT_ID = + EmulatorPlugin.PLUGIN_ID + ".deviceFramework"; + + private static final String DEV_FRAMEWORK_ELEM = "deviceFramework"; + + private static final String DEV_FRAMEWORK_IMPL_CLASS_ATTR = "class"; + + /** + * This is a singleton class. The instance is stored in this attribute + */ + private static DeviceFrameworkManager instance; + + /** + * A collection containing the classes provided by each extension + * implementer for retrieving framework data + */ + private Collection<IDeviceFrameworkSupport> allFrameworks = + new HashSet<IDeviceFrameworkSupport>(); + + /** + * Singleton private constructor + */ + private DeviceFrameworkManager() + { + populateModel(); + } + + /** + * Gets the instance of the class + * + * @return The instance of the class + */ + public static DeviceFrameworkManager getInstance() + { + if (instance == null) + { + instance = new DeviceFrameworkManager(); + } + + return instance; + } + + /** + * Retrieves all instances managed by every device framework + * which contributes with the deviceFramework extension point + * + * @return A collection containing all instances from all frameworks + */ + public Collection<IAndroidEmulatorInstance> getAllInstances() + { + Collection<IAndroidEmulatorInstance> allInstancesSet = + new LinkedHashSet<IAndroidEmulatorInstance>(); + for (IDeviceFrameworkSupport devFramework : allFrameworks) + { + Collection<IAndroidEmulatorInstance> devFrmInstances = devFramework.getAllInstances(); + if (devFrmInstances != null) + { + allInstancesSet.addAll(devFrmInstances); + } + } + + return allInstancesSet; + } + + /** + * Retrieve all registered and available instances + * + * @return List containing all registered and available instances + */ + public Collection<IAndroidEmulatorInstance> getAvailableInstances() + { + Collection<IAndroidEmulatorInstance> allInstances = getAllInstances(); + Collection<IAndroidEmulatorInstance> enabledInstances = + new ArrayList<IAndroidEmulatorInstance>(allInstances.size()); + + for (IAndroidEmulatorInstance emulatorInstance : allInstances) + { + if (emulatorInstance.isAvailable()) + { + enabledInstances.add(emulatorInstance); + } + } + return enabledInstances; + } + + /** + * Retrieve a collection of names of all the IAndroidEmulatorInstance + * of all Device frameworks... + * @return A collection of all instances of IAndroidEmulatorInstance. + */ + public Collection<String> getAllInstanceNames() + { + Collection<String> allInstancesNames = new LinkedHashSet<String>(); + for (IDeviceFrameworkSupport devFramework : allFrameworks) + { + for (IAndroidEmulatorInstance instance : devFramework.getAllInstances()) + { + allInstancesNames.add(instance.getName()); + } + } + + return allInstancesNames; + + } + + /** + * Retrieves the first occurrence of a IAndroidEmulatorInstance with the given name + * provided by any framework. + * @param name of the emulator instance to be retrieved. + * @return reference to a IAndroidEmulatorInstance with the given name or a null + * is there are no emulator instance with the given name. + */ + public IAndroidEmulatorInstance getInstanceByName(String name) + { + IAndroidEmulatorInstance instanceToReturn = null; + + for (IAndroidEmulatorInstance instance : getAllInstances()) + { + if (Platform.getOS().equals(Platform.WS_WIN32)) + { + if (instance.getName().toLowerCase().equals(name.toLowerCase())) + { + instanceToReturn = instance; + break; + } + } + else + { + if (instance.getName().equals(name)) + { + instanceToReturn = instance; + break; + } + } + + } + return instanceToReturn; + + } + + /** + * Retrieves all <b>started</b> instances managed by every device framework + * which contributes with the deviceFramework extension point + * + * @return A collection containing all started instances from all frameworks + */ + public Collection<IAndroidEmulatorInstance> getAllStartedInstances() + { + Collection<IAndroidEmulatorInstance> startedInstancesSet = + new HashSet<IAndroidEmulatorInstance>(); + for (IDeviceFrameworkSupport devFramework : allFrameworks) + { + Collection<IAndroidEmulatorInstance> devFrmInstances = devFramework.getAllInstances(); + if (devFrmInstances != null) + { + for (IAndroidEmulatorInstance instance : devFrmInstances) + { + if (instance.isStarted()) + { + startedInstancesSet.add(instance); + } + } + } + } + + return startedInstancesSet; + } + + /** + * Retrieves all <b>connected</b> instances managed by every device framework + * which contributes with the deviceFramework extension point + * + * @return A collection containing all connected instances from all frameworks + */ + public Collection<IAndroidEmulatorInstance> getAllConnectedInstances() + { + Collection<IAndroidEmulatorInstance> connectedInstancesSet = + new HashSet<IAndroidEmulatorInstance>(); + for (IDeviceFrameworkSupport devFramework : allFrameworks) + { + Collection<IAndroidEmulatorInstance> devFrmInstances = devFramework.getAllInstances(); + if (devFrmInstances != null) + { + for (IAndroidEmulatorInstance instance : devFrmInstances) + { + if (instance.isConnected()) + { + connectedInstancesSet.add(instance); + } + } + } + } + + return connectedInstancesSet; + } + + /** + * Retrieves all started instances host addresses managed by every + * device framework which contributes with the deviceFramework extension point + * + * @return A collection containing all instances from all frameworks + */ + public Set<String> getAllStartedInstancesHosts() + { + Set<String> hostSet = new HashSet<String>(); + for (IDeviceFrameworkSupport devFramework : allFrameworks) + { + Collection<IAndroidEmulatorInstance> devFrmInstances = devFramework.getAllInstances(); + if (devFrmInstances != null) + { + for (IAndroidEmulatorInstance instance : devFrmInstances) + { + if (instance.isStarted()) + { + hostSet.add(instance.getInstanceIdentifier()); + } + } + } + } + + return hostSet; + } + + /** + * Populates the allFrameworks collection with framework contributed + * classes for retrieving framework information. + */ + private void populateModel() + { + IExtensionRegistry extReg = Platform.getExtensionRegistry(); + IExtensionPoint extPoint = extReg.getExtensionPoint(DEV_FRAMEWORK_EXTENSION_POINT_ID); + IExtension[] extensions = extPoint.getExtensions(); + + for (IExtension aExtension : extensions) + { + IConfigurationElement[] configElements = aExtension.getConfigurationElements(); + for (IConfigurationElement aConfig : configElements) + { + if (aConfig.getName().equals(DEV_FRAMEWORK_ELEM)) + { + try + { + IDeviceFrameworkSupport devFramework = + (IDeviceFrameworkSupport) aConfig + .createExecutableExtension(DEV_FRAMEWORK_IMPL_CLASS_ATTR); + if (devFramework != null) + { + allFrameworks.add(devFramework); + } + } + catch (CoreException e) + { + // Do nothing. + // If a device framework cannot be instantiated, it will + // not be plugged to emulator core plugin. + } + } + } + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/devfrm/IDeviceFrameworkSupport.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/devfrm/IDeviceFrameworkSupport.java new file mode 100644 index 0000000..427f106 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/devfrm/IDeviceFrameworkSupport.java @@ -0,0 +1,46 @@ +/* +* 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.core.devfrm; + +import java.util.Collection; + +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; + +/** + * DESCRIPTION: + * Interface that must be implemented by every device framework + * that wishes to use the Android Emulator plug-ins + * + * RESPONSIBILITY: + * Provide every information that the Android Emulator plug-ins need + * to work with the registered framework + * + * COLABORATORS: + * None. + * + * USAGE: + * The class should be used by Eclipse only + */ +public interface IDeviceFrameworkSupport +{ + /** + * Retrieves a collection of the Android Emulator instances + * managed by this device framework + * + * @return Collection of the Android Emulator instances + */ + Collection<IAndroidEmulatorInstance> getAllInstances(); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/AbstractEmuCtProvider.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/AbstractEmuCtProvider.java new file mode 100644 index 0000000..8f9b094 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/AbstractEmuCtProvider.java @@ -0,0 +1,248 @@ +/* +* 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.core.emulationui; + +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.IViewSite; + +import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager; + +/** + * DESCRIPTION: + * This class is the abstract parent of all emulation views content providers + * + * RESPONSIBILITY: + * Provide common method implementation for the several emulation views + * content providers + * + * COLABORATORS: + * None. + * + * USAGE: + * The class is used when a emulation view content provider is instantiated + */ +public abstract class AbstractEmuCtProvider implements ITreeContentProvider +{ + /** + * The id to be used when constructing a "sent to" node + */ + public static final String SENT_TO_EMULATOR_ID = "sent_to"; + + /** + * The id to be used when constructing a "receive from" node + */ + public static final String RECEIVE_FROM_EMULATOR_ID = "received_from"; + + /** + * The parent of the entire tree + */ + private static IViewSite treeParent; + + /** + * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(Object) + */ + public Object getParent(Object element) + { + + Object parent = null; + + if (element instanceof EmuViewerNode) + { + EmuViewerNode nodeElement = (EmuViewerNode) element; + + if (nodeElement instanceof EmuViewerRootNode) + { + // The IViewSite object is the parent of the whole tree + parent = treeParent; + } + else + { + parent = nodeElement.getParent(); + } + } + else + { + warn("Tried to get parent of an object that is not an emulation tree node"); + } + + return parent; + } + + /** + * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(Object) + */ + public boolean hasChildren(Object object) + { + + boolean hasChildren = false; + + if (object instanceof EmuViewerNode) + { + EmuViewerNode nodeObject = (EmuViewerNode) object; + + // The node has children if its children collection is bigger than 0 in size + hasChildren = (nodeObject.getChildren().size() > 0); + + } + else + { + warn("Tried to test if an object that is not an emulation tree node has children"); + } + + return hasChildren; + } + + /** + * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(Object) + */ + public Object[] getElements(Object parent) + { + + Object[] elements; + + if (parent instanceof IViewSite) + { + + if (treeParent == null) + { + // Sets the treeParent attribute to store which view site is the parent of the + // whole tree. This is done only once for each content provider instance. + // Each provider instance is supposed to be used with a single view + treeParent = (IViewSite) parent; + } + + Collection<EmuViewerRootNode> emuNodeCollection = new HashSet<EmuViewerRootNode>(); + + Set<String> hostSet = + DeviceFrameworkManager.getInstance().getAllStartedInstancesHosts(); + + // A root node will be added per active emulator at the tree viewer + for (String host : hostSet) + { + EmuViewerRootNode node = new EmuViewerRootNode(host); + emuNodeCollection.add(node); + + addChildrenToRootNode(node); + } + + // Creating the array of elements (in this case, emulator root nodes) to be + // returned, when the parent is the view site itself + Object[] emuNodeArray = emuNodeCollection.toArray(new Object[emuNodeCollection.size()]); + elements = emuNodeArray; + + } + else + { + // When elements different from the view site are provided, the elements will be + // their children + elements = getChildren(parent); + } + + return elements; + } + + /** + * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(Object) + */ + public Object[] getChildren(Object parent) + { + + Set<EmuViewerNode> childrenCollection; + Object[] returnArray; + + if (parent instanceof EmuViewerNode) + { + // Firstly, try to retrieve the parent's children by means of the appropriate method + EmuViewerNode parentNode = (EmuViewerNode) parent; + + childrenCollection = parentNode.getChildren(); + + // If the provided element is an emulator root node, it is needed to test if the + // intermediate nodes were already created (they are not created in the first request). + // If they were not created, assure that when the content framework requests, the + // intermediate nodes will be found. + // + // This procedure guarantees that once an emulator is started, it has the intermediate + // nodes constructed even if no emulation is being performed. + if (parentNode instanceof EmuViewerRootNode) + { + String host = ((EmuViewerRootNode) parentNode).getEmulatorIdentifier(); + for (EmuViewerNode child : childrenCollection) + { + if (child.getChildren().size() == 0) + { + + addChildrenToLeafParentNode(child, host); + } + } + } + + // Creating the array of elements to be returned + returnArray = childrenCollection.toArray(new EmuViewerNode[childrenCollection.size()]); + + } + else + { + warn("Tried to get children of an object that is not an emulation tree node"); + returnArray = new Object[0]; + } + + return returnArray; + } + + /** + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + public void dispose() + { + // Do nothing + } + + /** + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(Viewer, Object, Object) + */ + public void inputChanged(Viewer v, Object oldInput, Object newInput) + { + // Do nothing + } + + /** + * Given a root node, adds children nodes to it + * + * @param root The root node that will receive the children nodes + */ + protected void addChildrenToRootNode(EmuViewerRootNode root) + { + + root.addChild(new EmuViewerNode(root, RECEIVE_FROM_EMULATOR_ID)); + root.addChild(new EmuViewerNode(root, SENT_TO_EMULATOR_ID)); + } + + /** + * Given a node, adds children leaf nodes to it + * + * @param leafParentNode The node that will receive the children leaf nodes + * @param host The identifier of the emulator that owns this sub-tree + */ + protected abstract void addChildrenToLeafParentNode(EmuViewerNode leafParentNode, String host); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/AbstractEmuLabelProvider.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/AbstractEmuLabelProvider.java new file mode 100644 index 0000000..812b40f --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/AbstractEmuLabelProvider.java @@ -0,0 +1,286 @@ +/* +* 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.core.emulationui; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.viewers.ViewerRow; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +/** + * DESCRIPTION: + * This class contains the common logic for classes that retrieve the labels + * that are presented at the emulation views + * + * RESPONSIBILITY: + * Provide basic support to emulation label providers. This include + * - Maintaining the column index for determining which data is to be retrieved from the + * beans + * - Maintaining which is the column at the view left edge, to draw the tree correctly at + * that position + * - Updating the viewer cells in a standardized way, giving support to coloring a line + * in alternative color + * + * COLABORATORS: + * None. + * + * USAGE: + * The class is used when a emulation view content provider is instantiated + */ +public abstract class AbstractEmuLabelProvider extends ColumnLabelProvider +{ + /** + * The column that is being updated by this label provider + */ + protected int columnIndex; + + /** + * The index of the column that is currently in the left edge of the viewer + */ + protected int firstColumnIndex; + + /** + * The host that contains the bean that should be painted in an alternative color + * or <code>null</code> if no bean will have alternative color + */ + private String alternativeColorHost; + + /** + * The id that identifies the bean that should be painted in an alternative color + * or <code>null</code> if no bean will have alternative color + */ + private long alternativeColorBeanId; + + /** + * The color that is used to paint highlighted nodes + */ + private final Color alternativeColor = + new Color(PlatformUI.getWorkbench().getDisplay(), 255, 255, 0); + + /** + * Sets the index of the column that is currently at the left edge of the viewer + * + * @param firstColumnIndex The index of the column that is currently at the left + * edge of the viewer + */ + public void setFirstColumnIndex(int firstColumnIndex) + { + this.firstColumnIndex = firstColumnIndex; + } + + /** + * Retrieves the index of the column that is currently at the left edge of the viewer + * + * @return firstColumnIndex The index of the column that is currently at the left + * edge of the viewer + */ + public int getFirstColumnIndex() + { + return firstColumnIndex; + } + + /** + * Sets the host that contains the bean that should be painted in an alternative color + * + * @param host The host that contains the bean to be colored in alternative way, or + * <code>null</code> if no bean shall be colored in alternative way + */ + public void setAlternativeColorHost(String host) + { + this.alternativeColorHost = host; + } + + /** + * Sets the id that identifies the bean that should be painted in an alternative color + * + * @param beanId The id that identifies the bean that should be painted in an alternative + * color or <code>null</code> if no bean will have alternative color + */ + public void setAlternativeColorBeanId(long beanId) + { + this.alternativeColorBeanId = beanId; + } + + /** + * @see org.eclipse.jface.viewers.CellLabelProvider#update(org.eclipse.jface.viewers.ViewerCell) + */ + @Override + public void update(ViewerCell cell) + { + // The instance column index is set with the current cell column index, as the logic + // contained in this class depends on this information. Then after the cell is + // updated according to the standard procedure, the column index field is reset so that + // it does not interfere with subsequent updates. + columnIndex = cell.getColumnIndex(); + super.update(cell); + columnIndex = firstColumnIndex; + + // Checks if the cell needs to be highlighted. This will be true if the values of + // alternativeColorHost and alternativeColorBeanId are different from null and -1 + if ((alternativeColorHost != null) && (alternativeColorBeanId != -1)) + { + + Object element = cell.getElement(); + // Only leaf nodes can be highlighted + if (element instanceof EmuViewerLeafNode) + { + // The next lines are used to check if the current element is the one to be + // highlighted. For that, the host and bean id needs to be compared to the + // alternativeColorHost and alternativeColorBeanId instance field values + EmuViewerLeafNode node = (EmuViewerLeafNode) element; + long beanId = node.getBeanId(); + EmuViewerRootNode root = (EmuViewerRootNode) node.getParent().getParent(); + String host = root.getEmulatorIdentifier(); + + if ((beanId == alternativeColorBeanId) && (host.equals(alternativeColorHost))) + { + // Highlighting the node + + cell.setBackground(alternativeColor); + + // Putting the node at the visible part of the tree + + ViewerRow highlightedRow = cell.getViewerRow(); + TreeItem highlightedItem = (TreeItem) highlightedRow.getItem(); + Tree tree = (Tree) cell.getControl(); + tree.showItem(highlightedItem); + } + } + + } + } + + /** + * @see org.eclipse.jface.viewers.ILabelProvider#getImage(Object) + */ + @Override + public Image getImage(Object element) + { + + Image imageToReturn = null; + + // The image should appear near the label at the first cell in the row. That is why + // a test is being performed for the first column. + if ((element instanceof EmuViewerNode) && (isProvidingForFirstColumn())) + { + if (element instanceof EmuViewerRootNode) + { + // Get an common icon for emulator nodes + + ImageDescriptor descriptor; + descriptor = + EmulatorPlugin.imageDescriptorFromPlugin( + EmulatorPlugin.PLUGIN_ID, IEmuIconPath.EMULATOR_ICON_PATH); + if (descriptor != null) + { + imageToReturn = descriptor.createImage(); + } + } + else if (element instanceof EmuViewerLeafNode) + { + // Delegate the get method to the concrete class for the leaf node icon + + imageToReturn = getLeafNodeIcon((EmuViewerLeafNode) element); + } + else + { + // Delegate the get method to the concrete class for the intermediate node icon + + imageToReturn = getIntermediateNodeIcon((EmuViewerNode) element); + } + } + + return imageToReturn; + } + + /** + * Tests if the resource being retrieved (text or image) is for a cell at the first column + * of the tree viewer + * + * @return True if it is providing for the first column. False otherwise + */ + protected boolean isProvidingForFirstColumn() + { + return firstColumnIndex == columnIndex; + } + + /** + * Retrieves the icon that shall be displayed next to the provided node element. The + * provided node is one of the intermediate nodes in the tree, and does not represent + * neither the emulator itself nor the leaf element + * + * @param node The tree node that will have the returned icon by its side + * + * @return The icon that shall be displayed near the provided node + */ + /** + * @see AbstractEmuLabelProvider#getIntermediateNodeIcon(EmuViewerNode) + */ + protected Image getIntermediateNodeIcon(EmuViewerNode node) + { + + Image imageToReturn = null; + ImageDescriptor descriptor; + + if (node.getNodeId().equals(AbstractEmuCtProvider.SENT_TO_EMULATOR_ID)) + { + descriptor = + EmulatorPlugin.imageDescriptorFromPlugin(EmulatorPlugin.PLUGIN_ID, + IEmuIconPath.SENT_TO_ICON_PATH); + } + else + { + descriptor = + EmulatorPlugin.imageDescriptorFromPlugin(EmulatorPlugin.PLUGIN_ID, + IEmuIconPath.RECEIVE_FROM_ICON_PATH); + } + + if (descriptor != null) + { + imageToReturn = descriptor.createImage(); + } + + return imageToReturn; + } + + /** + * Retrieves the icon that shall be displayed next to the provided leaf node element + * + * @param node The tree node that will have the returned icon by its side + * + * @return The icon that shall be displayed near the provided node + */ + protected abstract Image getLeafNodeIcon(EmuViewerLeafNode node); + + /** + * Gets the text referring to a particular leaf node, at the provided column. + * + * @param element The tree node that identifies the bean that needs to have information + * retrieved from + * @param columnIndex The id of the column that identifies which information to take from the + * bean + * + * @return The text from the bean at the specified column + */ + public abstract String getText(EmuViewerLeafNode node, int columnIndex); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerLeafNode.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerLeafNode.java new file mode 100644 index 0000000..4f1da74 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerLeafNode.java @@ -0,0 +1,71 @@ +/* +* 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.core.emulationui; + +/** + * DESCRIPTION: + * This is a utility class used to represent a leaf node of the emulation views + * tree viewer + * + * RESPONSIBILITY: + * Guarantee that no children is added to this leaf node + * + * COLABORATORS: + * None. + * + * USAGE: + * A class should construct an instance of this class whenever it wishes + * to add a leaf node to an emulation view tree + */ +public abstract class EmuViewerLeafNode extends EmuViewerNode +{ + /** + * Constructor + * + * @see EmuViewerNode#EmuViewerNode(EmuViewerNode, String) + */ + public EmuViewerLeafNode(EmuViewerNode parent, String nodeId) + { + super(parent, nodeId); + } + + /** + * @see EmuViewerNode#addChild(EmuViewerNode) + */ + @Override + public void addChild(EmuViewerNode child) + { + // Do nothing + } + + /** + * Retrieves the id that identifies the bean that provides data to this node + * + * @return The bean identifier + */ + public abstract long getBeanId(); + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + // For leaf nodes, identify which is the concrete class name and append to that name + // the associated bean id + return getClass().getSimpleName() + ":" + getBeanId(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerNode.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerNode.java new file mode 100644 index 0000000..f452760 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerNode.java @@ -0,0 +1,152 @@ +/* +* 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.core.emulationui; + +import java.util.HashSet; +import java.util.Set; + +/** + * DESCRIPTION: + * This class represents a node in the tree presented in a emulation view + * + * RESPONSIBILITY: + * Guarantee the tree structure by maintaining the parent/child relationship + * + * COLABORATORS: + * None. + * + * USAGE: + * A class should construct an instance of this class whenever it wishes + * to add a node to an emulation view tree + */ +public class EmuViewerNode +{ + /** + * The parent node of this node + */ + private final EmuViewerNode parent; + + /** + * An id that identifies the node type. + * The id meaning is defined by the user + */ + private final String nodeId; + + /** + * The error message to use as the node label + * If <code>null</code>, use regular label resolution + */ + private String errorMessage = null; + + /** + * A set containing all children of this node + */ + private final Set<EmuViewerNode> children = new HashSet<EmuViewerNode>(); + + /** + * Constructor. + * + * @param parent The parent node of this node + * @param nodeId An id that identifies the node type + */ + public EmuViewerNode(EmuViewerNode parent, String nodeId) + { + this.parent = parent; + this.nodeId = nodeId; + } + + /** + * Retrieves the node's parent + * + * @return The parent node + */ + public EmuViewerNode getParent() + { + return parent; + } + + /** + * Retrieves the id of this node. The id meaning is defined by the class user + * + * @return The id of this node + */ + public String getNodeId() + { + return nodeId; + } + + /** + * Adds a new child to this node + * + * @param child The child to be added to the node + */ + public void addChild(EmuViewerNode child) + { + children.add(child); + } + + /** + * Retrieves all this node's children + * + * @return A set containing all children of this node + */ + public Set<EmuViewerNode> getChildren() + { + return children; + } + + /** + * Sets an error message to display as the node label. + * If <code>null</code>, the regular label resolution is used + * + * @param errorMessage An error message to display or <code>null</code> if it is + * desired to have regular label resolution for this node + */ + public void setErrorMessage(String errorMessage) + { + this.errorMessage = errorMessage; + } + + /** + * Tests if this node has an error message assigned + * + * @return True if an error message was assigned; false otherwise + */ + public boolean hasErrorMessage() + { + return errorMessage != null; + } + + /** + * Retrieves the error message assigned to this node + * + * @return The error message, or <code>null</code> if no error message was assigned + */ + public String getErrorMessage() + { + return errorMessage; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + // For generic/intermediate nodes use the node id itself + return getNodeId(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerRootNode.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerRootNode.java new file mode 100644 index 0000000..0188835 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/EmuViewerRootNode.java @@ -0,0 +1,94 @@ +/* +* 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.core.emulationui; + +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import com.motorola.studio.android.emulator.core.exception.InstanceNotFoundException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.utils.EmulatorCoreUtils; + +/** + * DESCRIPTION: + * This class represents the parent of all nodes in the tree presented in a emulation view + * It must have reference to the emulator host, so that the tree is separated in + * several emulator sub-trees + * + * RESPONSIBILITY: + * To be the root of the emulator tree and maintain information about which emulator is + * owner of the sub-tree that has this node as root + * + * COLABORATORS: + * None. + * + * USAGE: + * A class should construct an instance of this class whenever an emulator information is to be + * included at an emulation view + */ +public class EmuViewerRootNode extends EmuViewerNode +{ + /** + * The emulator identifier (serial port number) + */ + private final String serial; + + /** + * Constructor. + * + * @param identifier The identifier of the emulator that owns the sub-tree starting at this node + */ + public EmuViewerRootNode(String identifier) + { + super(null, "ROOT"); + this.serial = identifier; + } + + /** + * Gets the host of the emulator that owns the sub-tree starting at this node + * + * @return The emulator host + */ + public String getEmulatorIdentifier() + { + return serial; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + String classString; + + // For emulator root nodes, the toString method should provide the emulator instance + // name. If it is not possible to retrieve the instance name, print the host itself + String serial = getEmulatorIdentifier(); + try + { + IAndroidEmulatorInstance instance = + EmulatorCoreUtils.getAndroidInstanceByIdentifier(serial); + classString = instance.getName(); + } + catch (InstanceNotFoundException e) + { + warn("The instance could not be found for retrieving its name. Using serial port instead."); + classString = serial; + } + + return classString; + } +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/IEmuIconPath.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/IEmuIconPath.java new file mode 100644 index 0000000..164f403 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/IEmuIconPath.java @@ -0,0 +1,30 @@ +/* +* 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.core.emulationui; + +/** + * This interface contains the paths to the icons that are used by the emulation views + */ +public interface IEmuIconPath +{ + // Emulation root node icon + String EMULATOR_ICON_PATH = "resource/emulator.png"; + + // Emulation intermediate nodes icons + String SENT_TO_ICON_PATH = "resource/sentbyemulator.png"; + + String RECEIVE_FROM_ICON_PATH = "resource/receivebyemulator.png"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/SrcDestComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/SrcDestComposite.java new file mode 100644 index 0000000..a70194e --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/emulationui/SrcDestComposite.java @@ -0,0 +1,439 @@ +/* +* 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.core.emulationui; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * DESCRIPTION: + * This is a composite that is used by several emulation UI elements to choose source and + * destination elements + * + * RESPONSIBILITY: + * Provide means for the user to choose which emulator and phone number will be involved + * in a emulation + * + * COLABORATORS: + * None. + * + * USAGE: + * Add the composite to a UI element that needs to have a emulator and a phone number + * chosen by the user + */ +public class SrcDestComposite extends Composite +{ + /** + * Message that will be shown near the emulator combo + */ + private String emulatorLabelStr; + + /** + * Message that will be shown near the phone number text field + */ + private String phoneNumberLabelStr; + + /** + * Emulator currently selected + */ + private String selectedEmulator; + + /** + * Phone number currently selected + */ + private String selectedPhoneNumber; + + /** + * True if the composite is valid and can provide information to the user class + * False if not. + */ + private boolean isValid = false; + + /** + * Error message to be shown to the user if the composite data is not valid + */ + private String errorMessage = + NLS.bind(EmulatorNLS.ERR_SrcDestComposite_InvalidFillingBase, + EmulatorNLS.ERR_SrcDestComposite_InvalidFillingPhoneNumber, + EmulatorNLS.ERR_SrcDestComposite_InvalidFillingEmulator); + + // Widgets + private Combo runningEmulatorsCombo; + + private Text phoneNumberText; + + // attribute for calculating label sizes (for layout purposes) + private FontMetrics fontMetrics = null; + + /** + * Constructor. + * + * @param parent The parent composite of this one + * @param style Style of the composite. See constants at SWT class + * @param showSrcControls True if this composite should show + * the emulation source controls. False otherwise + * @param isEmulatorSrc True if this composite will have the emulator + * part as source in emulation. False if the phone number + * will be the source + */ + public SrcDestComposite(Composite parent, int style, boolean showSrcControls, + boolean isEmulatorSrc) + { + super(parent, style); + + GridLayout layout = new GridLayout(2, false); + layout.marginHeight = 5; + layout.marginWidth = 5; + layout.verticalSpacing = 5; + layout.horizontalSpacing = 2; + this.setLayout(layout); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + this.setLayoutData(data); + + // initialize font metrics + GC gc = new GC(this); + gc.setFont(this.getFont()); + fontMetrics = gc.getFontMetrics(); + gc.dispose(); + + if (isEmulatorSrc) + { + // When emulator is the source part, its UI is build prior to phone number UI, + // and appropriate labels are used for both + debug("Using emulator as source"); + emulatorLabelStr = EmulatorNLS.UI_SrcDestComposite_OriginatingRunningEmulatorLabel; + phoneNumberLabelStr = EmulatorNLS.UI_SrcDestComposite_DestinationPhoneNumberLabel; + if (showSrcControls) + { + debug("Showing source controls"); + createEmulatorUI(); + } + createPhoneNumberUI(); + } + else + { + // When phone number is the source part, its UI is build prior to emulator UI, + // and appropriate labels are used for both + debug("Using phone number as source"); + emulatorLabelStr = EmulatorNLS.UI_SrcDestComposite_DestinationRunningEmulatorLabel; + phoneNumberLabelStr = EmulatorNLS.UI_SrcDestComposite_OriginatingPhoneNumberLabel; + if (showSrcControls) + { + debug("Showing source controls"); + createPhoneNumberUI(); + } + createEmulatorUI(); + } + + addListeners(); + + // call the check method to refresh error message. + checkData(); + + } + + /** + * Build the emulator part controls + */ + private void createEmulatorUI() + { + + Label runningEmulatorsLabel = new Label(this, SWT.NONE); + runningEmulatorsLabel.setText(emulatorLabelStr); + GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false); + data.widthHint = getLabelWidthHint(runningEmulatorsLabel); + runningEmulatorsLabel.setLayoutData(data); + + this.runningEmulatorsCombo = new Combo(this, SWT.BORDER | SWT.READ_ONLY); + data = new GridData(SWT.FILL, SWT.FILL, true, false); + this.runningEmulatorsCombo.setLayoutData(data); + populateEmulatorCombo(); + + } + + /** + * Build the phone number part controls + */ + private void createPhoneNumberUI() + { + + Label phoneNumberLabel = new Label(this, SWT.NONE); + phoneNumberLabel.setText(phoneNumberLabelStr); + GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false); + data.widthHint = getLabelWidthHint(phoneNumberLabel); + phoneNumberLabel.setLayoutData(data); + + this.phoneNumberText = new Text(this, SWT.BORDER); + data = new GridData(SWT.FILL, SWT.FILL, true, false); + this.phoneNumberText.setLayoutData(data); + this.phoneNumberText.setTextLimit(40); + + } + + /** + * Add listeners to the composite controls + */ + private void addListeners() + { + + if (runningEmulatorsCombo != null) + { + runningEmulatorsCombo.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + selectedEmulator = getCurrentlySelectedIdentifier(); + checkData(); + } + }); + } + + if (phoneNumberText != null) + { + phoneNumberText.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + selectedPhoneNumber = phoneNumberText.getText(); + checkData(); + } + }); + } + } + + /** + * Defines the width hint to be used for the given label on a GridData object. + * + * @param label the label to calculate the width hint for + * + * @return the width hint + */ + private int getLabelWidthHint(Label label) + { + int widthHint = Dialog.convertHorizontalDLUsToPixels(fontMetrics, label.getText().length()); + return Math.max(widthHint, label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x); + } + + /** + * Populates the emulator combo box with the currently running emulators + */ + private void populateEmulatorCombo() + { + + // Populating emulator combo with all running emulator names + // Besides, keeping an array of the identifiers as combo data. + Map<String, String> identifiersAndNames = new HashMap<String, String>(); + Collection<IAndroidEmulatorInstance> startedInstances = + DeviceFrameworkManager.getInstance().getAllStartedInstances(); + for (IAndroidEmulatorInstance instance : startedInstances) + { + identifiersAndNames.put(instance.getInstanceIdentifier(), instance.getName()); + } + + String[] instanceNamesArray = new String[identifiersAndNames.size()]; + String[] identifiersArray = new String[identifiersAndNames.size()]; + int i = 0; + + Set<String> identifiers = identifiersAndNames.keySet(); + for (String identifier : identifiers) + { + + String viewerName = identifiersAndNames.get(identifier); + + // It is VERY important that the index used at the data array is equal to the + // index used at the items array. According to the selected item in the combo, the + // corresponding identifier is retrieved from the data array in the future + instanceNamesArray[i] = viewerName; + identifiersArray[i] = identifier; + i++; + } + + runningEmulatorsCombo.setItems(instanceNamesArray); + runningEmulatorsCombo.setData(identifiersArray); + + // if there is just one emulator in the combo list, + // it will be chose by default. + if (runningEmulatorsCombo.getItemCount() == 1) + { + runningEmulatorsCombo.select(0); + selectedEmulator = getCurrentlySelectedIdentifier(); + checkData(); + } + + } + + /** + * Retrieve the identifier of the selected instance at Android Emulator combo box + * + * @return The identifier, or an empty string if no emulator is selected + */ + private String getCurrentlySelectedIdentifier() + { + + String currentlySelectedSerial = ""; + int index = runningEmulatorsCombo.getSelectionIndex(); + + if (index >= 0) + { + String[] serials = (String[]) runningEmulatorsCombo.getData(); + currentlySelectedSerial = serials[index]; + + } + + return currentlySelectedSerial; + } + + /** + * Get the emulator identifier that was selected by the user + * + * @return The selected emulator identifier + */ + public String getSelectedEmulator() + { + return selectedEmulator; + } + + /** + * Get the phone number that was typed by the user + * + * @return The phone number typed by the user + */ + public String getSelectedPhoneNumber() + { + return selectedPhoneNumber; + } + + /** + * Tests if the values chosen/typed by the user are valid + * By invoking this method, the user class is able to know if it can proceed + * + * @return True if the user has chosen valid values. False otherwise + */ + public boolean isValid() + { + return isValid; + } + + /** + * Retrieves the error message to be shown to the user if the composite is + * not valid + * + * @return The error message if the composite is not valid, or <code>null</code> if + * the composite is valid and no error message should be displayed. + */ + public String getErrorMessage() + { + return errorMessage; + } + + /** + * Check if the data entered by the user is correct and set instance variables + * to store the test results + */ + private void checkData() + { + + isValid = false; + + boolean isEmulatorValid = false; + boolean isPhoneNumberValid = false; + + boolean isUsingPhoneNumber = (phoneNumberText != null); + boolean isUsingEmulator = (runningEmulatorsCombo != null); + + // Tests if emulator selection is valid. + // + // If the emulator combo is null, that means that the user decided not to use it. In + // this case, it will always be valid. Otherwise, the combo selection needs to be + // not null and not blank + if ((!isUsingEmulator) || ((selectedEmulator != null) && (!selectedEmulator.equals("")))) + { + isEmulatorValid = true; + } + + // Tests if phone number selection is valid. + // + // If the phone number text is null, that means that the user decided not to use it. In + // this case, it will always be valid. Otherwise, the text field selection needs to be + // not null, not blank and can be parsed to double (that means that the contents are + // composed by numerals only) + if (!isUsingPhoneNumber) + { + isPhoneNumberValid = true; + } + else if ((selectedPhoneNumber != null) && (!selectedPhoneNumber.equals(""))) + { + Pattern p = Pattern.compile("(\\d)+"); + Matcher m = p.matcher(selectedPhoneNumber); + isPhoneNumberValid = m.matches(); + } + + // Based on previous checks, determine if the composite state is valid + if (isEmulatorValid && isPhoneNumberValid) + { + isValid = true; + errorMessage = null; + } + else + { + // If not valid, an error message will be shown. The following calculations + // are for determining which error has happened to build the message + String phoneNumberError = ""; + String emulatorError = ""; + + if (isUsingPhoneNumber && (!isPhoneNumberValid)) + { + phoneNumberError = EmulatorNLS.ERR_SrcDestComposite_InvalidFillingPhoneNumber; + } + if (isUsingEmulator && (!isEmulatorValid)) + { + emulatorError = EmulatorNLS.ERR_SrcDestComposite_InvalidFillingEmulator; + } + + errorMessage = + NLS.bind(EmulatorNLS.ERR_SrcDestComposite_InvalidFillingBase, + phoneNumberError, emulatorError); + } + + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceNotFoundException.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceNotFoundException.java new file mode 100644 index 0000000..c58a9a3 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceNotFoundException.java @@ -0,0 +1,58 @@ +/* +* 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.core.exception; + +import com.motorola.studio.android.common.exception.AndroidException; + +/** + * Represents an exception thrown every time a Android Emulator instance is not found + * at the device framework. + */ +@SuppressWarnings("serial") +public class InstanceNotFoundException extends AndroidException +{ + /** + * Creates a new InstanceNotFoundException object. + */ + public InstanceNotFoundException() + { + } + + /** + * @param message the message used by the Exception. + */ + public InstanceNotFoundException(String message) + { + super(message); + } + + /** + * @param cause the associated cause. + */ + public InstanceNotFoundException(Throwable cause) + { + super(cause); + } + + /** + * @param message the message used by the Exception. + * @param cause the associated cause. + */ + public InstanceNotFoundException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceStartException.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceStartException.java new file mode 100644 index 0000000..2e328cf --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceStartException.java @@ -0,0 +1,57 @@ +/* +* 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.core.exception; + +import com.motorola.studio.android.common.exception.AndroidException; + +/** + * Represents an exception thrown if the emulator instance cannot be started + */ +@SuppressWarnings("serial") +public class InstanceStartException extends AndroidException +{ + /** + * Creates a new InstanceStartException object. + */ + public InstanceStartException() + { + } + + /** + * @param message the message used by the Exception. + */ + public InstanceStartException(String message) + { + super(message); + } + + /** + * @param cause the associated cause. + */ + public InstanceStartException(Throwable cause) + { + super(cause); + } + + /** + * @param message the message used by the Exception. + * @param cause the associated cause. + */ + public InstanceStartException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceStopException.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceStopException.java new file mode 100644 index 0000000..beadb1d --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/InstanceStopException.java @@ -0,0 +1,57 @@ +/* +* 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.core.exception; + +import com.motorola.studio.android.common.exception.AndroidException; + +/** + * Represents an exception thrown if the emulator instance cannot be stopped + */ +@SuppressWarnings("serial") +public class InstanceStopException extends AndroidException +{ + /** + * Creates a new InstanceStopException object. + */ + public InstanceStopException() + { + } + + /** + * @param message the message used by the Exception. + */ + public InstanceStopException(String message) + { + super(message); + } + + /** + * @param cause the associated cause. + */ + public InstanceStopException(Throwable cause) + { + super(cause); + } + + /** + * @param message the message used by the Exception. + * @param cause the associated cause. + */ + public InstanceStopException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/SkinException.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/SkinException.java new file mode 100644 index 0000000..79c96cd --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/SkinException.java @@ -0,0 +1,58 @@ +/* +* 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.core.exception; + +import com.motorola.studio.android.common.exception.AndroidException; + +/** + * Represents an exception thrown every time the skin construction + * results in error. + */ +@SuppressWarnings("serial") +public class SkinException extends AndroidException +{ + /** + * Creates a new SkinException object. + */ + public SkinException() + { + } + + /** + * @param message the message used by the Exception. + */ + public SkinException(String message) + { + super(message); + } + + /** + * @param cause the associated cause. + */ + public SkinException(Throwable cause) + { + super(cause); + } + + /** + * @param message the message used by the Exception. + * @param cause the associated cause. + */ + public SkinException(String message, Throwable cause) + { + super(message, cause); + } +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/StartCancelledException.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/StartCancelledException.java new file mode 100644 index 0000000..e9d8105 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/StartCancelledException.java @@ -0,0 +1,58 @@ +/* +* 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.core.exception; + +import com.motorola.studio.android.common.exception.AndroidException; + +/** + * Represents the exception thrown when the user cancels the emulator + * start process + */ +@SuppressWarnings("serial") +public class StartCancelledException extends AndroidException +{ + /** + * Creates a new StartCancelledException object. + */ + public StartCancelledException() + { + } + + /** + * @param message the message used by the Exception. + */ + public StartCancelledException(String message) + { + super(message); + } + + /** + * @param cause the associated cause. + */ + public StartCancelledException(Throwable cause) + { + super(cause); + } + + /** + * @param message the message used by the Exception. + * @param cause the associated cause. + */ + public StartCancelledException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/StartTimeoutException.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/StartTimeoutException.java new file mode 100644 index 0000000..dba69f0 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/exception/StartTimeoutException.java @@ -0,0 +1,58 @@ +/* +* 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.core.exception; + +import com.motorola.studio.android.common.exception.AndroidException; + +/** + * Represents the exception thrown when the emulator start process cannot end + * within the set timeout + */ +@SuppressWarnings("serial") +public class StartTimeoutException extends AndroidException +{ + /** + * Creates a new StartTimeoutException object. + */ + public StartTimeoutException() + { + } + + /** + * @param message the message used by the Exception. + */ + public StartTimeoutException(String message) + { + super(message); + } + + /** + * @param cause the associated cause. + */ + public StartTimeoutException(Throwable cause) + { + super(cause); + } + + /** + * @param message the message used by the Exception. + * @param cause the associated cause. + */ + public StartTimeoutException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/AbstractInputLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/AbstractInputLogic.java new file mode 100644 index 0000000..cb3cd60 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/AbstractInputLogic.java @@ -0,0 +1,55 @@ +/* +* 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.core.model; + +/** + * Basic implementation of the IInputLogic interface. + * Provides common methods for all types of input + * + */ +public abstract class AbstractInputLogic implements IInputLogic +{ + /** + * The instance associated to this input + */ + private IAndroidEmulatorInstance instance; + + /** + * Retrieves the instance associated with this input + * @return + */ + public IAndroidEmulatorInstance getInstance() + { + return instance; + } + + /** + * Executes whatever is necessary before sending keys + */ + public void init(IAndroidEmulatorInstance instance) + { + this.instance = instance; + } + + /** + * Executes whatever is necessary before disposing the object + */ + public void dispose() + { + //do nothing + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IAndroidEmulatorInstance.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IAndroidEmulatorInstance.java new file mode 100644 index 0000000..1542798 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IAndroidEmulatorInstance.java @@ -0,0 +1,228 @@ +/* +* 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.core.model; + +import java.io.File; +import java.util.Properties; + +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.swt.widgets.Composite; + +import com.motorola.studio.android.emulator.core.exception.InstanceStopException; + +/** + * DESCRIPTION: + * This class represents the Android Emulator instance contract. + * + * RESPONSIBILITY: + * Define which information is required from a device that wishes to use + * the Android Emulator viewer. + * + * COLABORATORS: + * None. + * + * USAGE: + * Use the methods to retrieve information from an Android Emulator device instance + */ +public interface IAndroidEmulatorInstance +{ + /** + * Gets the instance name in simplified format + * + * @return The instance name + */ + String getName(); + + /** + * Gets the instance name in full format + * + * @return The instance name + */ + String getFullName(); + + /** + * Gets the identifier for this instance, that is available when the emulator is started. + * + * @return the instance identifier + */ + String getInstanceIdentifier(); + + /** + * Sets the current layout being used by this instance + * + * @param layoutName The new layout + */ + void setCurrentLayout(String layoutName); + + /** + * Gets the current known status of the flip or slide + * + * @return True if the flip/slide is closed; false otherwise + */ + String getCurrentLayout(); + + /** + * Sets the parameter used to determine if this instance has CLI display or not + * + * @param hasCli True if the instance has CLI; false otherwise + */ + void setHasCli(boolean hasCli); + + /** + * Gets the parameter used to determine if this instance has CLI display or not + * + * @return True if the instance has CLI; false otherwise + */ + boolean getHasCli(); + + /** + * Sets the Android protocol object created when connecting to the VM + * + * @param handle The Android protocol object provided when connecting + * the protocol + */ + void setProtocolHandle(ProtocolHandle handle); + + /** + * Gets the Android protocol object object that identifies the protocol connection + * + * @returns The Android protocol object representing the connection of this + * instance + */ + ProtocolHandle getProtocolHandle(); + + /** + * Gets the id of the skin logic being used for this instance + * + * @return The skin id + */ + String getSkinId(); + + /** + * Gets the path of the files being used to draw the skin for this instance + * + * @return A pointer to the folder that contains the files to be used + * to draw the skin for this instance + */ + File getSkinPath(); + + /** + * Tests if the instance is started + * + * @return True if it is started; false otherwise + */ + boolean isStarted(); + + /** + * Test if the instance is connected, i.e. + * The communication protocol is running + * + * @return True if it is connected; false otherwise + */ + boolean isConnected(); + + /** + * Test if the instance is available, i.e. + * The instance type is available for the current SDK setup. + * + * @return True if it's available; false otherwise + */ + boolean isAvailable(); + + /** + * Stops the Android Emulator instance + * + * @param force whether the stop should be forced or not + * + * @throws InstanceStopException If the instance fails to stop + */ + void stop(boolean force) throws InstanceStopException; + + /** + * Retrieves the collection of all instance properties + * + * @return The collection of instance properties + */ + public Properties getProperties(); + + /** + * Retrieves the input logic used by this instance to send data to the emulator + * + * @return The input logic used by this instance + */ + public IInputLogic getInputLogic(); + + /** + * Gets the emulator process associated to this instance when it's running + * @return the Process representing the emulator process + */ + public Process getProcess(); + + /** + * Sets the emulator process associated to this emulator instance while it's running + * @param process + */ + public void setProcess(Process process); + + /** + * Performs any needed operations to change the instance orientation/rotation + * + * @param args Additional data provided by the skin to perform the operation + */ + public void changeOrientation(String args); + + /** + * Get the Android target that the instance is compliant to + * + * @return Android target that the instance is compliant to + */ + public String getTarget(); + + /** + * Get the Android API level that the instance is compliant to + * + * @return Android API level that the instance is compliant to + */ + public int getAPILevel(); + + /** + * Get the Android Emulator window handle + * + * @return Android target that the instance is compliant to + */ + public long getWindowHandle(); + + /** + * Sets the handle of the emulator window associated with the instance + * + * @param handle + */ + public void setWindowHandle(long handle); + + /** + * + * @param contentComposite + */ + public void setComposite(Composite composite); + + /** + * + * @return + */ + public Composite getComposite(); + + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IEmulatorView.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IEmulatorView.java new file mode 100644 index 0000000..b87efa6 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IEmulatorView.java @@ -0,0 +1,36 @@ +/* +* 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.core.model; + +/** + * DESCRIPTION: + * Defines the method that every Android Emulator view should have + * for self-updating + * + * RESPONSIBILITY: + * Define the method that allows the Android Emulator views to self + * update + * + * COLABORATORS: + * None. + * + * USAGE: + * Call the interface method for view refreshing. + */ +public interface IEmulatorView +{ + void refreshView(); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IInputLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IInputLogic.java new file mode 100644 index 0000000..2cb9c67 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/model/IInputLogic.java @@ -0,0 +1,56 @@ +/* +* 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.core.model; + +import java.util.Properties; + +public interface IInputLogic +{ + void init(IAndroidEmulatorInstance instance); + + void dispose(); + + /** + * Send a key press event + * + * @param character the correspondent character + * @param keycode the keycode of the key pressed + * @param keyCodeMap the skin keycode map + */ + void sendKey(int character, int keycode, Properties keyCodeMap); + + /** + * Send a click event + * + * @param code the code to be sent + * @param pressed key pressed - yes or no + */ + void sendClick(int code, boolean pressed); + + /** + * Send a click event + * + * @param code the code to be sent + * @param pressed key pressed - yes or no + */ + void sendClick(String code, boolean pressed); + + void sendMouseUp(int x, int y); + + void sendMouseDown(int x, int y); + + void sendMouseMove(int x, int y); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/AndroidPressKey.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/AndroidPressKey.java new file mode 100644 index 0000000..e66bae0 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/AndroidPressKey.java @@ -0,0 +1,251 @@ +/* +* 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.core.skin; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.StringTokenizer; + +import org.eclipse.swt.graphics.Rectangle; + +/** + * DESCRIPTION: + * This is a default implementation of the interface IAndroidEmulatorKey + * + * RESPONSIBILITY: + * - Provide an easy way to find the keys pressed during mouse interaction + * at skin + * - Provide means of retrieving the keysym associated with each key + * + * COLABORATORS: + * None. + * + * USAGE: + * Provide a coordinate to the isInsideKey methods to test if the coordinate + * is inside the key area + * Use the getKeysym of a key to retrieve the code that needs to be sent in a + * key event message to the server, informing that a key was pressed or released + */ +public class AndroidPressKey implements IAndroidKey +{ + // Constants used in the isFlipValid method + private static final int FLIP_SLIDE_OPENED_ONLY = 0; + + private static final int FLIP_SLIDE_CLOSED_ONLY = 1; + + private static final int FLIP_SLIDE_OPENED_AND_CLOSED = 2; + + /** + * Fields that can be found in an ordinary key.xml file + */ + private final String name; + + private final String toolTip; + + private final String keycode; + + + + private final Rectangle keyArea; + + private final int flipSlideEnabledCode; + + private final Collection<String> morphingModeCollection = new LinkedHashSet<String>(); + + /** + * Creates a new AndroidPressKey object. + * + * @param name The key name. This is usually a human readable skin, that + * provides key identification + * @param keycode The code that will be sent to server if the key is pressed + * @param toolTip The text that will be shown as the key tool tip. + * @param startx X coordinate of the upper left corner of the key + * @param starty Y coordinate of the upper left corner of the key + * @param endx X coordinate of the lower right corner of the key + * @param endy Y coordinate of the lower right corner of the key + * @param morphingModes A comma separated list of morphing modes to which this + * key applies, or null if not applicable + * @param flipenabled true if this key is valid in closed flip mode; + * false if the key is valid in opened flip mode + */ + public AndroidPressKey(String name, String keycode, String toolTip, int startx, int starty, + int endx, int endy, String morphingModes, int flipEnabledCode) + { + this(name, keycode, toolTip, new Rectangle(startx, starty, endx - startx, endy - starty), + morphingModes, flipEnabledCode); + } + + /** + * Creates a new AndroidPressKey object. + * + * @param name The key name. This is usually a human readable skin, that + * provides key identification + * @param keycode The code that will be sent to server if the key is pressed + * @param toolTip The text that will be shown as the key tool tip. + * @param key A rectangle that represents the key area at skin + * @param morphingModes A comma separated list of morphing modes to which this + * key applies, or null if not applicable + * @param flipenabled true if this key is valid in closed flip mode; + * false if the key is valid in opened flip mode + */ + public AndroidPressKey(String name, String keycode, String toolTip, Rectangle key, + String morphingModes, int flipEnabledCode) + { + this.name = name; + this.keycode = keycode; + this.toolTip = toolTip; + this.keyArea = key; + this.flipSlideEnabledCode = flipEnabledCode; + + if (morphingModes != null) + { + StringTokenizer st = new StringTokenizer(morphingModes, ","); + String token; + + while (st.hasMoreTokens()) + { + token = st.nextToken(); + morphingModeCollection.add(token); + } + } + } + + /** + * @see IAndroidKey#getKeysym() + */ + public String getKeysym() + { + return keycode; + } + + /** + * @see IAndroidKey#isInsideKey(int, int) + */ + public boolean isInsideKey(int x, int y) + { + return keyArea.contains(x, y); + } + + /** + * Retrieves the X coordinate of the lower right corner of the key + * + * @return X coordinate of the lower right corner of the key + */ + public int getEndx() + { + return keyArea.x + keyArea.width; + } + + /** + * Retrieves the Y coordinate of the lower right corner of the key + * + * @return Y coordinate of the lower right corner of the key + */ + public int getEndy() + { + return keyArea.y + keyArea.height; + } + + /** + * Tests if the key is valid in the current flip/slide mode + * + * @param isFlipSlideClosed True if the flip/slide is currently closed + * False if the flip/slide is currently opened + * + * @return true if the key is valid in the current flip/slide mode; false otherwise + */ + public boolean isFlipSlideValid(boolean isFlipSlideClosed) + { + boolean flipSlideValid = false; + + if ((flipSlideEnabledCode == FLIP_SLIDE_OPENED_AND_CLOSED) + || (isFlipSlideClosed && (flipSlideEnabledCode == FLIP_SLIDE_CLOSED_ONLY)) + || (!isFlipSlideClosed && (flipSlideEnabledCode == FLIP_SLIDE_OPENED_ONLY))) + { + flipSlideValid = true; + } + + return flipSlideValid; + } + + /** + * Retrieves the key name + * + * @return The key name + */ + public String getName() + { + return name; + } + + /** + * Retrieves the tool tip text of the key. + * + * @return The tool tip text of the key. + */ + public String getToolTip() + { + return toolTip; + } + + /** + * Retrieves the X coordinate of the upper left corner of the key + * + * @return X coordinate of the upper left corner of the key + */ + public int getStartx() + { + return keyArea.x; + } + + /** + * Retrieves the Y coordinate of the upper left corner of the key + * + * @return Y coordinate of the upper left corner of the key + */ + public int getStarty() + { + return keyArea.y; + } + + /** + * Retrieves a rectangle that represents the key area at skin + * + * @return A rectangle that represents the key area at skin + */ + public Rectangle getKeyArea() + { + return keyArea; + } + + /** + * Tests if the key applies to the provided morphing mode + * + * @param morphingMode The morphing mode name + * @return true if the key applies to the morphing mode; false otherwise + */ + public boolean hasMorphingMode(String morphingMode) + { + if (morphingMode != null) + { + return morphingModeCollection.contains(morphingMode); + } + else + { + return false; + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/AndroidSkinBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/AndroidSkinBean.java new file mode 100644 index 0000000..32d7262 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/AndroidSkinBean.java @@ -0,0 +1,123 @@ +/* +* 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.core.skin; + +import java.util.HashMap; +import java.util.Map; + +/** + * DESCRIPTION: + * This bean holds data from the skin.xml file + * + * RESPONSIBILITY: + * - Provide an easy way to retrieve data read from skin.xml files + * + * COLABORATORS: + * None. + * + * USAGE: + * Call any of the interface methods to add or retrieve data to the class model + */ +public class AndroidSkinBean +{ + private final Map<String, Integer> skinPropertiesMap = new HashMap<String, Integer>(); + + /** + * Adds a skin property to the bean + * + * @param key The skin property key to use + * @param value The value of the skin property + */ + public void addSkinPropertyValue(String key, int value) + { + skinPropertiesMap.put(key, value); + } + + /** + * Retrieves a value of a skin property identified by key + * + * @param key The key that identifies the desired property + * + * @return The value of the desired property + */ + public int getSkinPropertyValue(String key) + { + if (skinPropertiesMap.get(key) != null) + { + return skinPropertiesMap.get(key); + } + else + { + return 0; + } + } + + /** + * Tests if open external display information is available at the skin + * which properties are stored at this bean + * + * @return True if open external display information is available; + * false otherwise + */ + public boolean isOpenExternalDisplayAvailable() + { + boolean result = true; + Integer testObj1 = skinPropertiesMap.get(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_WIDTH); + Integer testObj2 = skinPropertiesMap.get(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_HEIGHT); + + // If any of the width and height information is not available + // it is considered that there is not enough information about + // the open external display + if ((testObj1 == null) || (testObj2 == null)) + { + result = false; + } + + return result; + } + + /** + * Tests if external display information is available at the skin + * which properties are stored at this bean + * + * @return True if external display information is available; + * false otherwise + */ + public boolean isExternalDisplayAvailable() + { + boolean result = true; + Integer testObj1 = skinPropertiesMap.get(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_WIDTH); + Integer testObj2 = skinPropertiesMap.get(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_HEIGHT); + + // If any of the width and height information is not available + // it is considered that there is not enough information about + // the external display + if ((testObj1 == null) || (testObj2 == null)) + { + result = false; + } + + return result; + } + + public double getEmbeddedViewScale() + { + Integer testObj1 = skinPropertiesMap.get("embeddedViewScale"); + + return testObj1.intValue() / 10.0; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/IAndroidKey.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/IAndroidKey.java new file mode 100644 index 0000000..9e3237e --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/IAndroidKey.java @@ -0,0 +1,57 @@ +/* +* 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.core.skin; + +import org.eclipse.swt.graphics.Rectangle; + +/** + * This interface should be used by anyone who wishes to implement a class + * which contain key information for Motorola handsets + */ +public interface IAndroidKey +{ + /** + * This method returns the keysym code associated to this key + * + * @return The keysym code + */ + String getKeysym(); + + /** + * This method tests if a certain (x, y) coordinate is inside this key + * + * @param x The X coordinate + * @param y The Y coordinate + * + * @return true if the provided coordinate is internal to the key; false otherwise + */ + boolean isInsideKey(int x, int y); + + /** + * Retrieves a rectangle that corresponds to the drawing area of the + * key at the skin + * + * @return The key area + */ + Rectangle getKeyArea(); + + /** + * Retrieves the text tool tip of the key. + * + * @return The text tool tip of the key. + */ + String getToolTip(); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/IAndroidSkin.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/IAndroidSkin.java new file mode 100644 index 0000000..48b85f6 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/IAndroidSkin.java @@ -0,0 +1,163 @@ +/* +* 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.core.skin; + +import java.util.Collection; +import java.util.Properties; + +import org.eclipse.sequoyah.vnc.vncviewer.config.EclipsePropertiesFileHandler; +import org.eclipse.sequoyah.vnc.vncviewer.config.IPropertiesFileHandler; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.RGB; + +import com.motorola.studio.android.emulator.core.exception.SkinException; + +/** + * This interface must be implemented by anyone who wishes to contribute to the + * skin extension point + */ +public interface IAndroidSkin +{ + IPropertiesFileHandler DEFAULT_PROPS_HANDLER = new EclipsePropertiesFileHandler(); + + String DEFAULT_VNC_CONFIG_FILE = "resources/vnc_viewer.conf"; + + /** + * Retrieves an image data object containing one pressed image pixels and attributes + * + * @return An image data object containing pixels and image attributes + * + * @throws SkinException If any problem occurs while retrieving the image data. + * If a new exception is being created, it is expected that it provides a message + * to display to the user + */ + ImageData getPressedImageData(String layoutName) throws SkinException; + + /** + * Retrieves an image data object containing one released image pixels and attributes + * + * @return An image data object containing pixels and image attributes + * + * @throws SkinException If any problem occurs while retrieving the image data + * If a new exception is being created, it is expected that it provides a message + * to display to the user + */ + ImageData getReleasedImageData(String layoutName) throws SkinException; + + /** + * Retrieves an image data object containing one enter image pixels and attributes + * + * @return An image data object containing pixels and image attributes + * + * @throws SkinException If any problem occurs while retrieving the image data + * If a new exception is being created, it is expected that it provides a message + * to display to the user + */ + ImageData getEnterImageData(String layoutName) throws SkinException; + + /** + * Retrieves a collection containing all keys that are supported by the + * handset represented by this skin + * + * @return The key collection read from skin + */ + Collection<IAndroidKey> getKeyDataCollection(String layoutName); + + /** + * Retrieves a bean containing all skin data that does not refer to the keys + * + * @return The skin bean + * + * @throws SkinException If any problem occurs while retrieving the skin data + * If a new exception is being created, it is expected that it provides a message + * to display to the user + */ + AndroidSkinBean getSkinBean(String layoutName) throws SkinException; + + /** + * Tests if flip is supported by the phone represented by this skin + * + * @return true if flip is supported; false otherwise + */ + boolean isFlipSupported(); + + /** + * Set where the skin files are located based on the emulator root dir + * + * @param emulatorInstallDir Root of emulator installation + * + * @throws SkinException If the path provided does not contain a valid skin + */ + void setSkinFilesPath(String emulatorInstallDir) throws SkinException; + + /** + * Retrieves the names of all available layouts of the skin + * + * @return A collection containing the names of all available layouts + */ + public Collection<String> getAvailableLayouts(); + + /** + * Checks if the current layout is rotated (i.e. demands screen rotation) + */ + boolean isSwapWidthHeightNeededAtLayout(String layoutName); + + /** + * Retrieves the command to send to the emulator to switch screen + * + * @return The command to send to the emulator to switch screen + */ + String getLayoutScreenCommand(String layoutName); + + /** + * Finds which layout comes next to referenceLayout + * + * @param referenceLayout The layout to be used as reference on next layout calculation + * + * @return The next layout name + */ + public String getNextLayout(String referenceLayout); + + /** + * Finds which layout is previous to referenceLayout + * + * @param referenceLayout The layout to be used as reference on previous layout calculation + * + * @return The previous layout name + */ + public String getPreviousLayout(String referenceLayout); + + /** + * Retrieves what is the background color to be applied at the provided layout + * + * @param layoutName The layout name in which to apply the background color + * + * @return A RGB object describing the color + */ + public RGB getBackgroundColor(String layoutName); + + /** + * @return + */ + Properties getKeyCodes(); + + /** + * Return the dpad-rotation if present on a given layout or 0 otherwise. + * @param layoutName + * @return + */ + int getDpadRotation(String layoutName); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/ISkinFrameworkConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/ISkinFrameworkConstants.java new file mode 100644 index 0000000..f32a7cb --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/ISkinFrameworkConstants.java @@ -0,0 +1,33 @@ +/* +* 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.core.skin; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +/** + * Interface containing constants that are relevant to the Android Emulator Skin + * Contribution Framework + */ +interface ISkinFrameworkConstants +{ + String SKIN_EXTENSION_POINT_ID = EmulatorPlugin.PLUGIN_ID + ".skin"; + + String SKIN_INFO_ATTR = "skinInfo"; + + String SKIN_NAME_ATTR = "skinName"; + + String SKIN_ID_ATTR = "skinId"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/ISkinKeyXmlTags.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/ISkinKeyXmlTags.java new file mode 100644 index 0000000..68fe9fa --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/ISkinKeyXmlTags.java @@ -0,0 +1,90 @@ +/* +* 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.core.skin; + +/** + * Interface that contains the tags used into skin.xml and key.xml files + */ +public interface ISkinKeyXmlTags +{ + String SKIN_CONFIG = "skinConfig"; + + String SKIN_DOUBLE_SCREEN = "doubleScreen"; + + String SKIN_DOUBLE_VIEW = "doubleView"; + + String SKIN_KEYPAD_AREAS_NUMBER = "keypadAreasNumber"; + + String SKIN_VIEW_ZOOM_SCALE = "viewZoomScale"; + + String SKIN_MEMORY_SIZE = "memorySize"; + + String SKIN_REFRESH_RATE = "refreshRate"; + + String SKIN_INTERNAL_VIEW_X = "internalViewX"; + + String SKIN_INTERNAL_VIEW_Y = "internalViewY"; + + String SKIN_INTERNAL_VIEW_WIDTH = "internalViewWidth"; + + String SKIN_INTERNAL_VIEW_HEIGHT = "internalViewHeight"; + + String SKIN_INTERNAL_VIEW_DEPTH = "internalViewDepth"; + + String SKIN_OPEN_EXTERNAL_VIEW_X = "openExternalViewX"; + + String SKIN_OPEN_EXTERNAL_VIEW_Y = "openExternalViewY"; + + String SKIN_OPEN_EXTERNAL_VIEW_WIDTH = "openExternalViewWidth"; + + String SKIN_OPEN_EXTERNAL_VIEW_HEIGHT = "openExternalViewHeight"; + + String SKIN_OPEN_EXTERNAL_COLOR_DEPTH = "openExternalColorDepth"; + + String SKIN_EMBEDDED_VIEW_SCALE = "embeddedViewScale"; + + String SKIN_EXTERNAL_VIEW_X = "externalViewX"; + + String SKIN_EXTERNAL_VIEW_Y = "externalViewY"; + + String SKIN_EXTERNAL_VIEW_WIDTH = "externalViewWidth"; + + String SKIN_EXTERNAL_VIEW_HEIGHT = "externalViewHeight"; + + String SKIN_EXTERNAL_VIEW_DEPTH = "externalViewDepth"; + + String KEY_SKIN_KEY_INFO = "skinkeyinfo"; + + String KEY_DATA = "keyData"; + + String KEY_NAME = "name"; + + String KEY_CODE = "keycode"; + + String KEY_TOOLTIP = "hint"; + + String KEY_START_X = "startx"; + + String KEY_START_Y = "starty"; + + String KEY_END_X = "endx"; + + String KEY_END_Y = "endy"; + + String KEY_MORPHING_MODE = "morphingMode"; + + String KEY_FLIP_ENABLED = "flipenabled"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/SkinFramework.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/SkinFramework.java new file mode 100644 index 0000000..77e3e09 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/skin/SkinFramework.java @@ -0,0 +1,219 @@ +/* +* 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.core.skin; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.InvalidRegistryObjectException; +import org.eclipse.osgi.util.NLS; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** +* DESCRIPTION: +* This class is the entrance point to the Android Emulator Skin Contribution Framework +* module. Through this, other modules are able to retrieve information about +* all plugins plugged at com.motorola.studio.android.emulator.skin extension point. +* +* RESPONSIBILITY: +* - Provide information about plugged skins +* +* COLABORATORS: +* None. +* +* USAGE: +* The user gets the instance of SkinFramework and calls one of its +* public methods to get information regarding installed plugins +*/ +public class SkinFramework implements ISkinFrameworkConstants +{ + /** + * A map containing the ids of all plugged skins + */ + private final Map<String, String> skinIdMap = new HashMap<String, String>(); + + /** + * Creates a new SkinFramework object. + */ + public SkinFramework() + { + populateSkinIdMap(); + } + + /** + * Retrieves the IDs of every skin that is plugged to this framework + * + * USAGE: Any client plugin that wishes to have a collection of available + * skins should call this method + * + * @return A collection of installed skin names + */ + public Collection<String> getAllInstalledSkinIds() + { + return skinIdMap.keySet(); + } + + /** + * Retrieves a skin object identified by the provided ID + * + * @param skinId The ID of the skin to be retrieved + * + * @return The skin object that have the ID provided + * + * @throws SkinException If the skin cannot be loaded by the skin framework + */ + public IAndroidSkin getSkinById(String skinId) throws SkinException + { + return getSkinById(skinId, null); + } + + /** + * Retrieves a skin object identified by the provided ID + * + * @param skinId The ID of the skin to be retrieved + * + * @param emulatorInstallDir Root of emulator installation + * + * @return The skin object that have the ID provided + * + * @throws SkinException If the skin cannot be loaded by the skin framework + */ + public IAndroidSkin getSkinById(String skinId, File emulatorInstallDir) throws SkinException + { + IAndroidSkin selectedSkin = null; + + if (skinId != null) + { + try + { + // If a skin is not found at the already loaded skins collection, + // one must be created + String extensionId = skinIdMap.get(skinId); + if (extensionId != null) + { + selectedSkin = + (IAndroidSkin) EclipseUtils.getExecutable(extensionId, SKIN_INFO_ATTR); + if (emulatorInstallDir != null) + { + selectedSkin.setSkinFilesPath(emulatorInstallDir.getAbsolutePath()); + } + } + else + { + warn("The skin " + skinId + + " was requested but not retrieved. It is not installed."); + throw new SkinException(NLS.bind( + EmulatorNLS.WARN_SkinFramework_SkinNotInstalled, skinId)); + } + } + catch (CoreException e) + { + error("It was not possible to load the IAndroidSkin object associated to " + skinId + + " skin. Cause: " + e.getMessage()); + + throw new SkinException(NLS.bind(EmulatorNLS.EXC_SkinFramework_CreateIAndroidSkin, + skinId)); + } + } + else + { + error("A null parameter as skin name was provided for retrieving a skin object"); + throw new SkinException(NLS.bind(EmulatorNLS.WARN_SkinFramework_SkinNotInstalled, + "\"\"")); + } + + if (selectedSkin == null) + { + // If no exception is thrown until this moment and the skin object is still null, + // then it is assumed that the plugin has problems. Those are the reasons that explain the assumption: + // 1. The only situation in which the EclipseUtils.getExecutable method returns null is + // when the provided name is non existent; + // 2. SKIN_INFO_ATTR is a constant that matches the constant from the skin extension + // specification. If the plugin was loaded even with a different name, Eclipse has failed on detecting + // if the declaring plugin was correctly built + + error("The skin plugin is not accordant with the skin extension point specification"); + throw new SkinException(EmulatorNLS.EXC_SkinFramework_CreateIAndroidSkin); + } + + return selectedSkin; + } + + /** + * Populates the skinIdMap map with the association of each skin name + * and its declaring extension identifier + */ + private void populateSkinIdMap() + { + String skinId; + String extensionId; + + IExtension[] skinExtensions = EclipseUtils.getInstalledPlugins(SKIN_EXTENSION_POINT_ID); + String currentId = ""; + + try + { + for (IExtension skinExtension : skinExtensions) + { + currentId = skinExtension.getUniqueIdentifier(); + IConfigurationElement[] elements = skinExtension.getConfigurationElements(); + + for (IConfigurationElement element : elements) + { + if (element.getName().equals(SKIN_INFO_ATTR)) + { + extensionId = skinExtension.getUniqueIdentifier(); + skinId = element.getAttribute(SKIN_ID_ATTR); + if (skinId != null) + { + skinIdMap.put(skinId, extensionId); + } + else + { + warn("A invalid skin extension was not loaded because it did not declare its ID"); + String title = EmulatorNLS.GEN_Warning; + String message = + NLS + .bind( + EmulatorNLS.WARN_SkinFramework_InvalidInstalledSkinsNotLoaded, + currentId); + EclipseUtils.showErrorDialog(title, message); + } + } + } + } + } + catch (InvalidRegistryObjectException e) + { + warn("There are invalid skin extensions that were not loaded due to an exception. Cause: " + + e.getMessage()); + String title = EmulatorNLS.GEN_Warning; + EclipseUtils.showErrorDialog(title, NLS.bind( + EmulatorNLS.WARN_SkinFramework_InvalidInstalledSkinsNotLoaded, currentId)); + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/EmulatorCoreUtils.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/EmulatorCoreUtils.java new file mode 100644 index 0000000..ec7f5cb --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/EmulatorCoreUtils.java @@ -0,0 +1,175 @@ +/* +* 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.core.utils; + +import java.util.Collection; +import java.util.HashSet; + +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IViewReference; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager; +import com.motorola.studio.android.emulator.core.exception.InstanceNotFoundException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.model.IEmulatorView; + +/** + * DESCRIPTION: + * Utilities for Android Emulator restricted use + * + * RESPONSIBILITY: + * Provide common utility methods that can be used by any + * Android Emulator plugin. + * + * COLABORATORS: + * None + * + * USAGE: + * This class should not be instantiated and its methods should be called statically. + */ +public class EmulatorCoreUtils +{ + /** + * Retrieves all views that implement IEmulatorView interface + * + * @return All views that implement IEmulatorView interface + */ + public static Collection<IEmulatorView> getAllAndroidViews() + { + final Collection<IEmulatorView> allViews = new HashSet<IEmulatorView>(); + + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + IWorkbenchWindow activeWindow = + PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (activeWindow != null) + { + IWorkbenchPage[] activePages = activeWindow.getPages(); + if (activePages != null) + { + for (IWorkbenchPage activePage : activePages) + { + if (activePage != null) + { + IViewReference[] allReferences = activePage.getViewReferences(); + for (IViewReference ref : allReferences) + { + IViewPart aView = ref.getView(false); + if ((aView != null) && (aView instanceof IEmulatorView)) + { + allViews.add((IEmulatorView) aView); + } + } + } + } + } + } + } + }); + + return allViews; + } + + /** + * Refresh all Emulator views + */ + public static void refreshEmulatorViews() + { + Collection<IEmulatorView> allViews = EmulatorCoreUtils.getAllAndroidViews(); + for (IEmulatorView view : allViews) + { + view.refreshView(); + } + } + + /** + * Retrieves a Android Emulator instance mapped by the provided IP address + * + * @param identifier The IP address associated with the desired instance + * + * @return The Android Emulator instance that is working on the given address + * + * @throws InstanceNotFoundException If the instance is not found at + * device framework + */ + public static IAndroidEmulatorInstance getAndroidInstanceByIdentifier(String identifier) + throws InstanceNotFoundException + { + IAndroidEmulatorInstance desiredDevice = null; + + Collection<IAndroidEmulatorInstance> instanceList = + DeviceFrameworkManager.getInstance().getAllInstances(); + for (IAndroidEmulatorInstance instance : instanceList) + { + String instanceIdentifier = instance.getInstanceIdentifier(); + if ((instanceIdentifier != null) && instanceIdentifier.equals(identifier)) + { + desiredDevice = instance; + break; + } + } + + if (desiredDevice == null) + { + throw new InstanceNotFoundException(); + } + + return desiredDevice; + } + + /** + * Retrieves a Android Emulator instance mapped by the provided + * protocol handle object + * + * @param handle The protocol handle object associated with the + * desired instance + * + * @return The Android Emulator instance that is working with + * the given protocol handle object + * + * @throws InstanceNotFoundException If the instance is not found at + * device framework + */ + public static IAndroidEmulatorInstance getAndroidInstanceByHandle(ProtocolHandle handle) + throws InstanceNotFoundException + { + IAndroidEmulatorInstance desiredDevice = null; + Collection<IAndroidEmulatorInstance> instanceList = + DeviceFrameworkManager.getInstance().getAllInstances(); + for (IAndroidEmulatorInstance instance : instanceList) + { + ProtocolHandle instanceHandle = instance.getProtocolHandle(); + if ((instanceHandle != null) && instanceHandle.equals(handle)) + { + desiredDevice = instance; + break; + } + } + + if (desiredDevice == null) + { + throw new InstanceNotFoundException(); + } + + return desiredDevice; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/TelnetAndroidInput.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/TelnetAndroidInput.java new file mode 100644 index 0000000..f598218 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/TelnetAndroidInput.java @@ -0,0 +1,416 @@ +/* +* 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.core.utils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.core.model.AbstractInputLogic; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.utilities.TelnetFrameworkAndroid; + +/** + * This class is responsible for sending input events to the Android Emulator + */ +@SuppressWarnings("serial") +public class TelnetAndroidInput extends AbstractInputLogic +{ + /* + * Event types + */ + public static final String EV_TYPE_SYN = "EV_SYN"; + + public static final String EV_TYPE_KEY = "EV_KEY"; + + public static final String EV_TYPE_ABS = "EV_ABS"; + + public static final String EV_TYPE_SW = "EV_SW"; + + public static final int EV_SW_LID = 0x00; + + public static final String EV_SYN_REPORT = "0"; + + public static final String EV_ABS_X = "ABS_X"; + + public static final String EV_ABS_Y = "ABS_Y"; + + private static final String EV_TOUCH = "BTN_TOUCH"; + + private final Map<Integer, String> SPECIAL_KEY_MAPPING = new HashMap<Integer, String>() + { + { + put(16777219, "KEY_LEFT"); // left + put(16777217, "KEY_UP"); // top + put(16777220, "KEY_RIGHT"); // right + put(16777218, "KEY_DOWN"); // bottom + } + }; + + /* + * Event types + */ + public static final int OPHONE_EV_TYPE_SYN = 0x00; + + public static final int OPHONE_EV_TYPE_KEY = 0x01; + + public static final int OPHONE_EV_TYPE_ABS = 0x03; + + public static final int OPHONE_EV_TYPE_SW = 0x05; + + public static final int OPHONE_EV_SW_LID = 0x00; + + public static final int OPHONE_EV_SYN_REPORT = 0x00; + + public static final int OPHONE_EV_ABS_X = 0x00; + + public static final int OPHONE_EV_ABS_Y = 0x01; + + private final Map<Integer, Integer> OPHONE_SPECIAL_KEY_MAPPING = + new HashMap<Integer, Integer>() + { + { + put(16777219, 0x069); // left + put(16777217, 0x067); // top + put(16777220, 0x06a); // right + put(16777218, 0x06c); // bottom + + } + }; + + private static final int OPHONE_KEY_BACKSPACE = 0x00e; + + private static final int OPHONE_KEY_SPACE = 0x039; + + private static final int OPHONE_KEY_ENTER = 0x01c; + + private static final int OPHONE_EV_TOUCH = 0x14A; + + // mouse position x + private int oldX; + + // mouse position y + private int oldY; + + // telnet connection + private final TelnetFrameworkAndroid telnet = new TelnetFrameworkAndroid(); + + /** + * Open the Telnet connection and initialize the communication + * + * @see com.motorola.studio.android.emulator.core.model.AbstractInputLogic#init(com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance) + */ + @Override + public void init(IAndroidEmulatorInstance instance) + { + super.init(instance); + + String deviceSerial = instance.getInstanceIdentifier(); + + String serial = deviceSerial.substring(deviceSerial.length() - 4, deviceSerial.length()); + try + { + telnet.connect("localhost", Integer.parseInt(serial)); + } + catch (IOException e) + { + //Do nothing + } + } + + /** + * Close Telnet connection + * + * @see com.motorola.studio.android.emulator.core.model.AbstractInputLogic#dispose() + */ + @Override + public void dispose() + { + try + { + telnet.disconnect(); + } + catch (IOException e) + { + // Do nothing + } + } + + /** + * Send mouse down event + * + * @see com.motorola.studio.android.emulator.core.model.IInputLogic#sendMouseDown(int, int) + */ + public void sendMouseDown(int x, int y) + { + sendAndroidEvent(EV_TYPE_ABS, EV_ABS_X, x); + sendAndroidEvent(EV_TYPE_ABS, EV_ABS_Y, y); + if (SdkUtils.isOphoneSDK()) + { + sendAndroidEvent(EV_TYPE_KEY, OPHONE_EV_TOUCH, true); + sendAndroidEvent(EV_TYPE_SYN, OPHONE_EV_SYN_REPORT, 0); + } + else + { + sendAndroidEvent(EV_TYPE_KEY, EV_TOUCH, true); + sendAndroidEvent(EV_TYPE_SYN, EV_SYN_REPORT, 0); + } + + oldX = x; + oldY = y; + } + + /** + * Send mouse up event + * + * @see com.motorola.studio.android.emulator.core.model.IInputLogic#sendMouseUp(int, int) + */ + public void sendMouseUp(int x, int y) + { + if (SdkUtils.isOphoneSDK()) + { + sendAndroidEvent(EV_TYPE_KEY, OPHONE_EV_TOUCH, false); + sendAndroidEvent(EV_TYPE_SYN, OPHONE_EV_SYN_REPORT, 0); + } + else + { + sendAndroidEvent(EV_TYPE_KEY, EV_TOUCH, false); + sendAndroidEvent(EV_TYPE_SYN, EV_SYN_REPORT, 0); + } + } + + /** + * Send mouse move event + * + * @see com.motorola.studio.android.emulator.core.model.IInputLogic#sendMouseMove(int, int) + */ + public void sendMouseMove(int x, int y) + { + if (oldX != x) + { + sendAndroidEvent(EV_TYPE_ABS, EV_ABS_X, x); + oldX = x; + } + if (oldY != y) + { + sendAndroidEvent(EV_TYPE_ABS, EV_ABS_Y, y); + oldY = y; + } + if (SdkUtils.isOphoneSDK()) + { + sendAndroidEvent(EV_TYPE_SYN, OPHONE_EV_SYN_REPORT, 0); + } + else + { + sendAndroidEvent(EV_TYPE_SYN, EV_SYN_REPORT, 0); + } + } + + /** + * Send a generic event to the emulator + * + * @param type event type + * @param keysym event definition + * @param pressed key pressed - yes or no + */ + public void sendAndroidEvent(String type, String keysym, boolean pressed) + { + sendAndroidEvent(type, keysym, pressed ? 1 : 0); + } + + public void sendAndroidEvent(String type, int keysym, boolean pressed) + { + sendAndroidEvent(type, keysym, pressed ? 1 : 0); + } + + private void sendAndroidEvent(String type, int keysym, int i) + { + try + { + telnet.write("event send " + type + ":" + keysym + ":" + i, null); + } + catch (IOException e) + { + StudioLogger.error("Failed to send generic event to Emulator"); + } + } + + /** + * Send a complete event to the Emulator + * Some events, like a key press, need two events (key pressed/released) + * to be executed. This method send both in order to execute the event. + * + * @param type event type + * @param keysym event definition + */ + public void sendAndroidEvent(String type, String keysym) + { + sendAndroidEvent(type, keysym, true); + sendAndroidEvent(type, keysym, false); + } + + /** + * Send a complete event to the Emulator + * Some events, like a key press, need two events (key pressed/released) + * to be executed. This method send both in order to execute the event. + * + * @param type event type + * @param keysym event definition + */ + public void sendAndroidEvent(String type, int keysym) + { + sendAndroidEvent(type, keysym, true); + sendAndroidEvent(type, keysym, false); + } + + /** + * Send a generic event to the emulator + * + * @param type event type + * @param keysym event definition + * @param value parameter + */ + private void sendAndroidEvent(String type, String keysym, int value) + { + try + { + telnet.write("event send " + type + ":" + keysym + ":" + value, null); + } + catch (IOException e) + { + StudioLogger.error("Failed to send generic event to Emulator"); + } + } + + + /* (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.model.IInputLogic#sendKey(int, int) + */ + public void sendKey(int character, int keycode, Properties keyCodeMap) + { + /* + * Check if it's a character + */ + if (character > 0) + { + String text = String.valueOf((char) character); + // check it's a blank space + if (text.equals(" ")) + { + if (SdkUtils.isOphoneSDK()) + { + sendAndroidEvent(EV_TYPE_KEY, OPHONE_KEY_SPACE); + } + else + { + sendAndroidEvent(EV_TYPE_KEY, "KEY_SPACE"); + } + } + // check if it's a backspace + else if (text.equals("\b")) + { + if (SdkUtils.isOphoneSDK()) + { + sendAndroidEvent(EV_TYPE_KEY, OPHONE_KEY_BACKSPACE); + } + else + { + sendAndroidEvent(EV_TYPE_KEY, "KEY_BACKSPACE"); + } + } + // check if it's an enter + else if (text.equals("\r")) + { + if (SdkUtils.isOphoneSDK()) + { + sendAndroidEvent(EV_TYPE_KEY, OPHONE_KEY_ENTER); + } + else + { + sendAndroidEvent(EV_TYPE_KEY, "KEY_ENTER"); + } + } + else + { + if (keyCodeMap != null) + { + String keyCode = keyCodeMap.getProperty(text.toUpperCase().trim()); + if (keyCode != null) + { + sendAndroidEvent(EV_TYPE_KEY, keyCode); + } + } + } + } + else + { + if (!SdkUtils.isOphoneSDK()) + { + String keycode_str = null; + if (SPECIAL_KEY_MAPPING.containsKey(keycode)) + { + keycode_str = SPECIAL_KEY_MAPPING.get(keycode); + } + if (keycode_str != null) + { + sendAndroidEvent(EV_TYPE_KEY, keycode_str); + } + } + else + { + if (OPHONE_SPECIAL_KEY_MAPPING.containsKey(keycode)) + { + keycode = OPHONE_SPECIAL_KEY_MAPPING.get(keycode); + } + if (keycode != -1) + { + sendAndroidEvent(EV_TYPE_KEY, keycode); + } + } + } + + } + + /* (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.model.IInputLogic#sendClick(int, boolean) + */ + public void sendClick(String code, boolean pressed) + { + sendAndroidEvent(EV_TYPE_KEY, code, pressed ? 1 : 0); + } + + public void sendClick(int code, boolean pressed) + { + sendAndroidEvent(EV_TYPE_KEY, code, pressed ? 1 : 0); + } + + public void sendWindowScale(double zoomFactor) + { + try + { + telnet.write("window scale " + zoomFactor, null); + } + catch (IOException e) + { + StudioLogger.error("Failed to send window scale to Emulator"); + } + + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/VncAndroidInput.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/VncAndroidInput.java new file mode 100644 index 0000000..b5383ca --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/core/utils/VncAndroidInput.java @@ -0,0 +1,96 @@ +/* +* 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.core.utils; + +import java.util.Properties; + +import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate; +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolMessage; + +import com.motorola.studio.android.emulator.core.model.AbstractInputLogic; + +public class VncAndroidInput extends AbstractInputLogic +{ + private static final int VNC_KEYEVENT_MESSAGE_CODE = 0x04; + + private static final int VNC_POINTEREVENT_MESSAGE_CODE = 0x05; + + private boolean buttonPressed; + + private void sendAndroidMouseEventMessage(int x, int y) + { + ProtocolMessage message = new ProtocolMessage(VNC_POINTEREVENT_MESSAGE_CODE); + message.setFieldValue("buttonMask", (buttonPressed ? 1 : 0)); + message.setFieldValue("x-position", x); + message.setFieldValue("y-position", y); + + try + { + PluginProtocolActionDelegate.sendMessageToServer(getInstance().getProtocolHandle(), + message); + } + catch (Exception e) + { + // Do nothing + } + } + + public void sendKey(int character, int keycode, Properties keyCodeMap) + { + ProtocolMessage message = new ProtocolMessage(VNC_KEYEVENT_MESSAGE_CODE); + message.setFieldValue("padding", 0); + message.setFieldValue("downFlag", 1); + message.setFieldValue("key", keycode); + + try + { + PluginProtocolActionDelegate.sendMessageToServer(getInstance().getProtocolHandle(), + message); + } + catch (Exception e) + { + // Do nothing + } + } + + public void sendClick(int code, boolean pressed) + { + //do nothing + } + + public void sendClick(String code, boolean pressed) + { + //do nothing + } + + public void sendMouseDown(int x, int y) + { + buttonPressed = true; + sendAndroidMouseEventMessage(x, y); + } + + public void sendMouseMove(int x, int y) + { + sendAndroidMouseEventMessage(x, y); + } + + public void sendMouseUp(int x, int y) + { + buttonPressed = false; + sendAndroidMouseEventMessage(x, y); + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/AndroidDeviceHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/AndroidDeviceHandler.java new file mode 100644 index 0000000..da411f5 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/AndroidDeviceHandler.java @@ -0,0 +1,61 @@ +/* +* 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; + +import org.eclipse.sequoyah.device.framework.model.IDeviceLauncher; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.model.handler.IDeviceHandler; + +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; + +/** + * DESCRIPTION: + * <br> + * This class represents a TmL IDeviceHandler for Android Emulator Instances. + * <br> + * RESPONSIBILITY: + * <br> + * - Create an IInstance object for Android Emulator Device Instances + * <br> + * COLABORATORS: + * <br> + * IDeviceHandler: implements this interface + * <br> + * USAGE: + * <br> + * This class is declared by the plugin.xml for the Android Emulator Device Instance declaration. + */ +public class AndroidDeviceHandler implements IDeviceHandler +{ + + /** + * Creates an Android Emulator Device Instance with the given id. + * + * @param id the instance id + */ + public IInstance createDeviceInstance(String id) + { + IInstance instance = new AndroidDeviceInstance(); + instance.setId(id); + return instance; + } + + public IDeviceLauncher createDeviceLauncher(IInstance instance) + { + return null; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/AndroidDeviceUtils.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/AndroidDeviceUtils.java new file mode 100644 index 0000000..82078c2 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/AndroidDeviceUtils.java @@ -0,0 +1,74 @@ +/* +* 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; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent.InstanceEventType; + +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic.LogicMode; + +public class AndroidDeviceUtils +{ + public static synchronized void fireDummyStartTransition(AndroidDeviceInstance instance, + String serialNumber) + { + // if instance is not already started, is not starting and is already associated to a VM... + boolean instanceStarted = instance.isStarted(); + boolean instanceIsStarting = instance.getStateMachineHandler().isTransitioning(); + boolean vmAlreadyUp = instance.hasDevice(); + instance.setNameSuffix(serialNumber + ", " + instance.getTarget()); + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, instance)); + + if (vmAlreadyUp && !instanceStarted && !instanceIsStarting) + { + info("The TmL Instance is not started/Starting, but the emulator/VM is already online. Execute a dummy start service to force a transition to start status..."); + + Map<Object, Object> attributes = new LinkedHashMap<Object, Object>(); + attributes.put(LogicMode.class, LogicMode.DO_NOTHING); + try + { + EmulatorPlugin.getStartServiceHandler().run(instance, attributes, + new NullProgressMonitor()); + } + catch (Exception e) + { + error("Failed to run the dummy start service on " + instance + " : " + + e.getMessage()); + } + } + } + + /** + * Verifies whether or not a given AndroidDeviceInstance is running. + * @param androidInstance + * @return true if instance is running, false otherwise. + */ + public static boolean isInstanceStarting(AndroidDeviceInstance androidInstance) + { + return androidInstance.getStateMachineHandler().isTransitioning(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/CreateAVDOnStartupListener.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/CreateAVDOnStartupListener.java new file mode 100644 index 0000000..13a87da --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/CreateAVDOnStartupListener.java @@ -0,0 +1,73 @@ +/*
+* 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;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.ui.IStartup;
+
+import com.motorola.studio.android.AndroidPlugin;
+import com.motorola.studio.android.adt.SdkUtils;
+import com.motorola.studio.android.common.preferences.DialogWithToggleUtils;
+import com.motorola.studio.android.emulator.device.handlers.OpenNewDeviceWizardHandler;
+import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
+
+public class CreateAVDOnStartupListener implements IStartup
+{
+ private static final String SDK_CREATE_NEW_AVD_KEY = "create.avd.on.startup";
+
+ private static final Lock lock = new ReentrantReadWriteLock().writeLock();
+
+ private static boolean executed = false;
+
+ public void earlyStartup()
+ {
+ AndroidPlugin.getDefault().addSDKLoaderListener(new Runnable()
+ {
+ public void run()
+ {
+ lock.lock();
+ if (!executed
+ && ((SdkUtils.getAllTargets() != null) && (SdkUtils.getAllTargets().length > 0))
+ && ((SdkUtils.getAllValidVms() != null) && (SdkUtils.getAllValidVms().length == 0)))
+ {
+ if (DialogWithToggleUtils.showQuestion(SDK_CREATE_NEW_AVD_KEY,
+ EmulatorNLS.UI_SdkSetup_CreateAVD_Title,
+ EmulatorNLS.UI_SdkSetup_CreateAVD_Message))
+ {
+ OpenNewDeviceWizardHandler handler = new OpenNewDeviceWizardHandler();
+ try
+ {
+ handler.execute(new ExecutionEvent());
+ }
+ catch (ExecutionException e)
+ {
+ //do nothing
+ lock.unlock();
+ }
+ }
+ executed = true;
+ }
+ lock.unlock();
+ }
+ });
+
+ }
+
+}
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/EmulatorDropSupportHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/EmulatorDropSupportHandler.java new file mode 100644 index 0000000..81ba24a --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/EmulatorDropSupportHandler.java @@ -0,0 +1,37 @@ +/*
+* 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;
+
+import org.eclipse.sequoyah.device.framework.model.IInstance;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.TransferData;
+
+import com.motorola.studio.android.devices.AbstractDeviceDropSupportHandler;
+import com.motorola.studio.android.emulator.EmulatorPlugin;
+
+public class EmulatorDropSupportHandler extends AbstractDeviceDropSupportHandler
+{
+
+ /* (non-Javadoc)
+ * @see org.eclipse.sequoyah.device.framework.model.IDeviceTypeDropSupport#canDrop(org.eclipse.sequoyah.device.framework.model.IInstance, org.eclipse.swt.dnd.TransferData, org.eclipse.swt.dnd.DropTargetEvent)
+ */
+ @Override
+ public boolean canDrop(IInstance instance, TransferData data, DropTargetEvent event)
+ {
+ return super.canDrop(instance, data, event)
+ && EmulatorPlugin.STATUS_ONLINE_ID.equals(instance.getStatus());
+ }
+}
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/IAndroidDeviceConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/IAndroidDeviceConstants.java new file mode 100644 index 0000000..bc6c7db --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/IAndroidDeviceConstants.java @@ -0,0 +1,30 @@ +/* +* 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; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +/** + * DESCRIPTION: + * This interface contains constants used by Android Device Plug-in + */ +public interface IAndroidDeviceConstants +{ + // CONTEXT HELP + String MAIN_PAGE_HELP = EmulatorPlugin.PLUGIN_ID + ".newdevmain"; + + String STARTUP_OPTIONS_HELP = EmulatorPlugin.PLUGIN_ID + ".newdevstartup"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/IDevicePropertiesConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/IDevicePropertiesConstants.java new file mode 100644 index 0000000..3064cb4 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/IDevicePropertiesConstants.java @@ -0,0 +1,132 @@ +/* +* 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; + +import java.io.File; + +/** + * DESCRIPTION: + * This interface contains the name and some default values of the Device Instance Properties + */ +public interface IDevicePropertiesConstants +{ + /** + * The key that identifies the description property of an Android Emulator + * device instance + */ + String deviceDescription = "Description"; + + /** + * The key that identifies the emulator type property of an Android Emulator + * device instance + */ + String emulatorDefId = "Emulator_Type"; + + /** + * The key that identifies the skin property of an Android Emulator + * device instance + */ + String skinId = "Skin_Plugin_Id"; + + /** + * The key that identifies the timeout property of an Android Emulator + * device instance + */ + String timeout = "Timeout"; + + /** + * The key that identifies the useVnc property of an Android Emulator + * device instance + */ + String useVnc = "UseVnc"; + + /** + * The key that identifies the useVnc property of an Android Emulator + * device instance + */ + String useProxy = "UseProxy"; + + /** + * The key that identifies the VM Target property of an Android Emulator + * device instance + */ + String vmTarget = "Vm_Target"; + + /** + * The key that identifies the VM ABI type property of an Android Emulator + * device instance + */ + String abiType = "Abi_Type"; + + /** + * The key that identifies the VM Skin property of an Android Emulator + * device instance + */ + String vmSkin = "Vm_Skin"; + + /** + * The key that identifies the VM Path property of an Android Emulator + * device instance + */ + String vmPath = "Vm_Path"; + + /** + * The key that identifies the command line arguments to be used + * when starting the emulator + */ + String commandline = "Command_Line"; + + /** + * AVD Config file properties + */ + String configSDCardPath = "sdcard.path"; + + String configSDCardSize = "sdcard.size"; + + /** + * The default vm path + */ + String defaultVmPath = System.getProperty("user.home") + File.separator + ".android" + + File.separator + "avd"; + + String defaultVmFolderSuffix = ".avd"; + + String defaultUseProxyValue = "false"; + + /** + * The default timeout value (ms), which must be used when creating a new emulator + * instance + */ + String defaultTimeoutValue = "120"; + + /** + * Whether to use snapshots + */ + + String useSnapshots = "UseSnapshot"; + + String defaultUseSnapshotValue = "false"; + + String saveSnapshot = "SaveSnapshot"; + + String dafaultSaveSnapshotValue = "false"; + + String startFromSnapshot = "startFromSnapshot"; + + String defaultstartFromSnapshotValue = "false"; + + String defaulSaveSnapshot = "false"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/SequoyahInstanceBackward.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/SequoyahInstanceBackward.java new file mode 100644 index 0000000..6ffcce7 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/SequoyahInstanceBackward.java @@ -0,0 +1,78 @@ +/*
+* 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sequoyah.device.common.utilities.exception.SequoyahException;
+import org.eclipse.sequoyah.device.framework.DevicePlugin;
+import org.eclipse.sequoyah.device.framework.factory.InstanceRegistry;
+import org.eclipse.sequoyah.device.framework.manager.InstanceManager;
+import org.eclipse.sequoyah.device.framework.model.IInstance;
+import org.eclipse.ui.IStartup;
+
+import com.motorola.studio.android.common.log.StudioLogger;
+import com.motorola.studio.android.emulator.device.refresh.InstancesListRefresh;
+
+/**
+ * This startup intent to iterate over the list of Android Emulator instances and change the emulator ID
+ * due the change of the plugin ids for the 1.3.0 release
+ *
+ */
+public class SequoyahInstanceBackward implements IStartup
+{
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.IStartup#earlyStartup()
+ */
+ public void earlyStartup()
+ {
+ boolean refreshNeeded = false;
+ List<IInstance> instances =
+ new ArrayList<IInstance>(InstanceRegistry.getInstance().getInstances());
+ for (IInstance oldInstance : instances)
+ {
+ if (oldInstance.getDeviceTypeId().equals(
+ "com.motorola.studio.android.emulator.device.androidDevice"))
+ {
+ try
+ {
+ InstanceRegistry.getInstance().addInstance(
+ InstanceManager.createInstance(oldInstance.getName(),
+ "com.motorola.studio.android.emulator.androidDevice",
+ DevicePlugin.SEQUOYAH_STATUS_OFF, oldInstance.getProperties()));
+ InstanceRegistry.getInstance().removeInstance(oldInstance);
+ }
+ catch (SequoyahException e)
+ {
+ StudioLogger.error(
+ SequoyahInstanceBackward.class,
+ "An error ocurred trying to backward old instance: "
+ + oldInstance.getName(), e);
+ }
+
+ refreshNeeded = true;
+ }
+ }
+ if (refreshNeeded)
+ {
+ InstancesListRefresh.refresh();
+ }
+ }
+
+}
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/SequoyahLogRedirector.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/SequoyahLogRedirector.java new file mode 100644 index 0000000..2180065 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/SequoyahLogRedirector.java @@ -0,0 +1,237 @@ +/* +* 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; + +import org.eclipse.sequoyah.device.common.utilities.logger.LoggerConstants; + +import com.motorola.studio.android.common.log.StudioLogger; + +/** + * DESCRIPTION: + * This class implements the TmL logger interface to redirect all logs from + * TmL to the log system used by the emulator + * + * RESPONSIBILITY: + * Delegate the logging requests from TmL to the same logger used by the emulator + * + * COLABORATORS: + * None. + * + * USAGE: + * An instance of this class is constructed during the emulator log startup. + * This class is not supposed to be constructed by clients + */ +public class SequoyahLogRedirector implements org.eclipse.sequoyah.vnc.utilities.logger.ILogger, + org.eclipse.sequoyah.device.common.utilities.logger.ILogger +{ + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#debug(java.lang.Object) + */ + public void debug(Object message) + { + if (message instanceof String) + { + StudioLogger.debug((String) message); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#error(java.lang.Object, java.lang.Object) + */ + public void error(Object message, Object throwable) + { + if (message instanceof String) + { + StudioLogger.error((String) message); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#error(java.lang.Object) + */ + public void error(Object message) + { + if (message instanceof String) + { + StudioLogger.error((String) message); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#fatal(java.lang.Object) + */ + public void fatal(Object message) + { + if (message instanceof String) + { + StudioLogger.fatal((String) message); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#info(java.lang.Object) + */ + public void info(Object message) + { + if (message instanceof String) + { + StudioLogger.info((String) message); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#log(java.lang.Object, java.lang.Object, java.lang.Object) + */ + public void log(Object priority, Object message, Object throwable) + { + log(priority, message); + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#log(java.lang.Object, java.lang.Object) + */ + public void log(Object priority, Object message) + { + String priorityStr = (String) priority; + if (message instanceof String) + { + if (priorityStr.equals(LoggerConstants.FATAL)) + { + StudioLogger.fatal((String) message); + } + else if (priorityStr.equals(LoggerConstants.ERROR)) + { + StudioLogger.error((String) message); + } + else if (priorityStr.equals(LoggerConstants.WARNING)) + { + StudioLogger.warn((String) message); + } + else if (priorityStr.equals(LoggerConstants.INFO)) + { + StudioLogger.info((String) message); + } + else if (priorityStr.equals(LoggerConstants.DEBUG)) + { + StudioLogger.debug((String) message); + } + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#warn(java.lang.Object) + */ + public void warn(Object message) + { + if (message instanceof String) + { + StudioLogger.warn((String) message); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#getCurrentLevel() + */ + public Object getCurrentLevel() + { + return LoggerConstants.TXT_ALL; + } + + //************************************************ + // FROM THIS POINT, NO METHODS WILL BE IMPLEMENTED + //************************************************ + + /* + * + */ + public void configureLogger(Object arg0) + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#log(java.lang.Object) + */ + public void log(Object arg0) + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#setLevel(java.lang.Object) + */ + public void setLevel(Object arg0) + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#setLogToConsole() + */ + public void setLogToConsole() + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#setLogToFile(java.lang.String, java.lang.String) + */ + public void setLogToFile(String arg0, String arg1) + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#setLogToFile(java.lang.String) + */ + public void setLogToFile(String arg0) + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.vnc.utilities.logger.ILogger#setLogToHTMLFile(java.lang.String) + */ + public void setLogToHTMLFile(String arg0) + { + //nothing to do here + } + + /* + * (non-Javadoc) + * @see org.eclipse.sequoyah.device.common.utilities.logger.ILogger#setLogToDefault() + */ + public void setLogToDefault() + { + //nothing to do here + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/TmLDeviceFrameworkSupport.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/TmLDeviceFrameworkSupport.java new file mode 100644 index 0000000..1d400cd --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/TmLDeviceFrameworkSupport.java @@ -0,0 +1,88 @@ +/* +* 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; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.sequoyah.device.common.utilities.exception.SequoyahException; +import org.eclipse.sequoyah.device.framework.DeviceUtils; +import org.eclipse.sequoyah.device.framework.factory.InstanceRegistry; +import org.eclipse.sequoyah.device.framework.model.IDeviceType; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.model.IService; +import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler; + +import com.motorola.studio.android.emulator.core.devfrm.IDeviceFrameworkSupport; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; + +/** + * DESCRIPTION: + * This class attaches the TmL device framework to the Android Emulator plug-ins + * + * RESPONSIBILITY: + * to work with the TmL device framework + * + * COLABORATORS: + * None. + * + * USAGE: + * The class should be used by Eclipse only + */ +public class TmLDeviceFrameworkSupport implements IDeviceFrameworkSupport +{ + /** + * @see IDeviceFrameworkSupport#getAllInstances() + */ + public Collection<IAndroidEmulatorInstance> getAllInstances() + { + List<IInstance> tmlInstances = InstanceRegistry.getInstance().getInstances(); + Collection<IAndroidEmulatorInstance> androidCollection = + new HashSet<IAndroidEmulatorInstance>(); + for (IInstance tmlInstance : tmlInstances) + { + if (tmlInstance instanceof IAndroidEmulatorInstance) + { + androidCollection.add((IAndroidEmulatorInstance) tmlInstance); + } + } + + return androidCollection; + } + + // This should be a contribution to TmL. + public static IStatus runService(IInstance instance, String serviceID, + Map<Object, Object> arguments, IProgressMonitor monitor) throws SequoyahException + { + IStatus runStatus = null; + IDeviceType deviceType = DeviceUtils.getDeviceType(instance); + for (IService service : deviceType.getServices()) + { + if (service.getId().equals(serviceID)) + { + ServiceHandler serviceHandler = (ServiceHandler) service.getHandler(); + runStatus = serviceHandler.run(instance, arguments, monitor); + break; + } + } + + return runStatus; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/AndroidEmuDefBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/AndroidEmuDefBean.java new file mode 100644 index 0000000..7f3c898 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/AndroidEmuDefBean.java @@ -0,0 +1,102 @@ +/* +* 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.definition; + +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * This class holds an emulator definition + * + */ +class AndroidEmuDefBean +{ + + // emulator name + private String name; + + // emulator skin ID + private String skinId; + + // emulator start logic + private AbstractStartAndroidEmulatorLogic startLogic = null; + + // startup emulator command arguments + private String arguments = NativeUIUtils.getDefaultCommandLine(); + + /** + * Create an emulator definition + * + * @param name emulator name + * @param skinId emulator skin ID + * @param skinSize emulator skin size + */ + AndroidEmuDefBean(String name, String skinId, String skinSize) + { + this.name = name; + this.skinId = skinId; + } + + /** + * Get emulator name + * + * @return emulator name + */ + String getName() + { + return name; + } + + /** + * Get emulator skin ID + * + * @return emulator skin ID + */ + String getSkinId() + { + return skinId; + } + + /** + * Get startup emulator command arguments + * + * @return emulator command line arguments + */ + String getCommandLineArguments() + { + return arguments; + } + + /** + * Get emulator start logic + * + * @return emulator start logic + */ + public AbstractStartAndroidEmulatorLogic getStartLogic() + { + return startLogic; + } + + /** + * Set emulator start logic + * @param startLogic emulator start logic class + */ + public void setStartLogic(AbstractStartAndroidEmulatorLogic startLogic) + { + this.startLogic = startLogic; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/AndroidEmuDefMgr.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/AndroidEmuDefMgr.java new file mode 100644 index 0000000..a1eeada --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/AndroidEmuDefMgr.java @@ -0,0 +1,265 @@ +/* +* 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.definition; + +import static com.motorola.studio.android.common.log.StudioLogger.error; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.sequoyah.device.common.utilities.PluginUtils; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.model.IInputLogic; +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic; + +/** + * Contains methods for managing Android emulator definitions + * + */ +public class AndroidEmuDefMgr implements IAndroidEmuDefConstants +{ + // emulator definitions + private static final Map<String, AndroidEmuDefBean> emuDefs = + new LinkedHashMap<String, AndroidEmuDefBean>(); + + private final static AndroidEmuDefMgr instance = new AndroidEmuDefMgr(); + + /** + * Initialize class + */ + private AndroidEmuDefMgr() + { + readExtensions(); + } + + /** + * Return class instance + * + * @return AndroidEmuDefMgr instance + */ + public static AndroidEmuDefMgr getInstance() + { + return instance; + } + + /** + * Read extension points to add emulator definitions + */ + private static void readExtensions() + { + IExtension[] emuDefExtensions = + EclipseUtils.getInstalledPlugins(EMULATOR_DEFINITION_EXTENSION_POINT); + + for (IExtension emuDefExtension : emuDefExtensions) + { + String id = emuDefExtension.getUniqueIdentifier(); + + // elements + IConfigurationElement[] elements = emuDefExtension.getConfigurationElements(); + + AndroidEmuDefBean bean = null; + + boolean extensionOk = true; + for (IConfigurationElement element : elements) + { + if (element.getName().equals(ELEMENT_SKIN)) + { + String skinId = element.getAttribute(ATT_SKIN_ID); + String skinSize = element.getAttribute(ATT_SKIN_SIZE); + + if (!skinId.equals("") && !skinSize.equals("")) + { + bean = new AndroidEmuDefBean(emuDefExtension.getLabel(), skinId, skinSize); + } + else + { + extensionOk = false; + } + } + } + + if (extensionOk) + { + emuDefs.put(id, bean); + } + } + } + + /** + * Get all emulator IDs + * + * @return all emulator IDs + */ + public Collection<String> getAllIds() + { + return emuDefs.keySet(); + } + + /** + * Retrieves the default emulator definition id, which should be initially set + * to the emulator devices being created + * + * @return The default emulator definition id + */ + public String getDefaultId() + { + String defaultId = ""; + + Collection<String> ids = getAllIds(); + + /* + * NOTE: This is not considering more than one type of device + */ + if (!ids.isEmpty()) + { + Object[] idsArray = ids.toArray(); + defaultId = idsArray[0].toString(); + } + + return defaultId; + } + + /** + * Get all emulator names + * + * @return all emulator names + */ + public String[] getAllNames() + { + String[] allNames = new String[emuDefs.size()]; + + int i = 0; + for (AndroidEmuDefBean bean : emuDefs.values()) + { + allNames[i++] = bean.getName(); + } + + return allNames; + } + + /** + * Get emulator name given its ID + * + * @param emuDefId emulator ID + * @return emulator name + */ + public String getName(String emuDefId) + { + String name = emuDefId; + AndroidEmuDefBean bean = emuDefs.get(emuDefId); + + if (bean != null) + { + name = bean.getName(); + } + + return name; + } + + /** + * Get emulator skin ID given its ID + * + * @param emuDefId emulator ID + * @return emulator skin ID + */ + public String getSkinId(String emuDefId) + { + String skinId = ""; + AndroidEmuDefBean bean = emuDefs.get(emuDefId); + + if (bean != null) + { + skinId = bean.getSkinId(); + } + + return skinId; + } + + /** + * Get startup emulator command arguments + * + * @param emuDefId emulator ID + * @return emulator command line arguments + */ + public String getCommandLineArgumentsForEmuDefinition(String emuDefId) + { + String arguments = ""; + AndroidEmuDefBean bean = emuDefs.get(emuDefId); + + if (bean != null) + { + arguments = bean.getCommandLineArguments(); + } + + return arguments; + } + + /** + * Retrieve the input logic of the given emulator definition + * @param emuDefId id of the extension that declare emulator definitions. + * @return the IAndroidLogic associated to the given emulator definitions + */ + public IInputLogic getInputLogic(String emuDefId, IAndroidEmulatorInstance instance) + { + + IInputLogic inputLogic = null; + + try + { + inputLogic = (IInputLogic) PluginUtils.getExecutable(emuDefId, "inputLogic"); + inputLogic.init(instance); + } + catch (Exception e) + { + error("Could not retrieve the input logic from definition " + emuDefId); + } + + return inputLogic; + } + + /** + * Get the start logic for the given emulator definition + * @param emuDefId id of the extension that declare emulator definitions. + * @return the IAndroidLogic associated to the given emulator definitions + */ + public AbstractStartAndroidEmulatorLogic getStartLogic(String emuDefId) + { + + AbstractStartAndroidEmulatorLogic startLogic = null; + AndroidEmuDefBean bean = emuDefs.get(emuDefId); + + try + { + if (bean.getStartLogic() == null) + { + bean.setStartLogic((AbstractStartAndroidEmulatorLogic) PluginUtils.getExecutable( + emuDefId, "startLogic")); + } + startLogic = bean.getStartLogic(); + } + catch (Exception e) + { + error("Could not retrieve the Start logic for " + emuDefId); + } + + return startLogic; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/IAndroidEmuDefConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/IAndroidEmuDefConstants.java new file mode 100644 index 0000000..92ef40a --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/definition/IAndroidEmuDefConstants.java @@ -0,0 +1,42 @@ +/* +* 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.definition; + +/** + * This interface contains constants used when managing Android emulator definitions + * + */ +public interface IAndroidEmuDefConstants +{ + String EMULATOR_DEFINITION_EXTENSION_POINT = + "com.motorola.studio.android.emulator.androidEmulatorDefinition"; + + String ELEMENT_SKIN = "skin"; + + String ATT_SKIN_ID = "id"; + + String ATT_SKIN_SIZE = "size"; + + String SKIN_SIZE_HVGA = "HVGA"; + + String SKIN_SIZE_HVGAL = "HVGA-L"; + + String SKIN_SIZE_HVGAP = "HVGA-P"; + + String SKIN_SIZE_QVGAL = "QVGA-L"; + + String SKIN_SIZE_QVGAP = "QVGA-P"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/handlers/OpenNewDeviceWizardHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/handlers/OpenNewDeviceWizardHandler.java new file mode 100644 index 0000000..64c295b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/handlers/OpenNewDeviceWizardHandler.java @@ -0,0 +1,52 @@ +/* +* 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.handlers; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.sequoyah.device.framework.ui.wizard.NewDeviceMenuWizard; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +public class OpenNewDeviceWizardHandler extends AbstractHandler +{ + + /* + * (non-Javadoc) + * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent) + */ + public Object execute(ExecutionEvent event) throws ExecutionException + { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() + { + + public void run() + { + NewDeviceMenuWizard wizard = new NewDeviceMenuWizard(); + wizard.setCurrentDeviceTypeId(EmulatorPlugin.DEVICE_ID); + WizardDialog dialog = + new WizardDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow() + .getShell(), wizard); + dialog.open(); + } + }); + + return null; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/init/InitServiceHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/init/InitServiceHandler.java new file mode 100644 index 0000000..a79d214 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/init/InitServiceHandler.java @@ -0,0 +1,145 @@ +/* +* 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.init; + +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.List; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent.InstanceEventType; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.model.IService; +import org.eclipse.sequoyah.device.framework.model.handler.IServiceHandler; + +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; + +/** + * DESCRIPTION: + * This class plugs the init procedure to a TmL service. This service implements the + * interface directly, because it causes the instance to have a particular behavior + * at the state machine. + * + * RESPONSIBILITY: + * Provide the initialization procedure to apply to every instance that + * is loaded at TmL device framework + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by Eclipse only + */ +public class InitServiceHandler implements IServiceHandler +{ + /** + * The parent service handler + */ + private IServiceHandler parent; + + /** + * The service that launches the handler + */ + private IService service; + + /** + * @see IServiceHandler#run(IInstance) + */ + public void run(IInstance instance) + { + if (instance instanceof IAndroidLogicInstance) + { + // The service definition defined (by convention) that + // stopped-dirty is the success state, and not available + // is the failure state. The exception is being thrown for + // the framework to set the state correctly. + instance.setStatus(EmulatorPlugin.STATUS_NOT_AVAILABLE); + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_TRANSITIONED, instance)); + } + + } + + /** + * @see IServiceHandler#newInstance() + */ + public IServiceHandler newInstance() + { + return new InitServiceHandler(); + } + + /** + * @see IServiceHandler#setParent(IServiceHandler) + */ + public void setParent(IServiceHandler handler) + { + this.parent = handler; + } + + /** + * @see IServiceHandler#setService(IService) + */ + public void setService(IService service) + { + this.service = service; + } + + /** + * @see IServiceHandler#updatingService(IInstance) + */ + public void updatingService(IInstance instance) + { + info("Updating init emulator service"); + } + + /** + * @see IServiceHandler#clone() + * @see Cloneable#clone() + */ + @Override + public Object clone() + { + IServiceHandler newHandler = newInstance(); + newHandler.setParent(parent); + newHandler.setService(service); + return newHandler; + } + + /** + * @see IServiceHandler#getParent() + */ + public IServiceHandler getParent() + { + return parent; + } + + /** + * @see IServiceHandler#getService() + */ + public IService getService() + { + return service; + } + + public IStatus singleInit(List<IInstance> instances) + { + return Status.OK_STATUS; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDevInstBuilder.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDevInstBuilder.java new file mode 100644 index 0000000..cee670c --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDevInstBuilder.java @@ -0,0 +1,108 @@ +/* +* 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 java.util.Properties; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.sequoyah.device.framework.model.IInstanceBuilder; + +/** + * DESCRIPTION: + * <br> + * This class represents a TmL IInstanceBuilder for Android Emulator Instances. + * <br> + * It is only necessary because TmL's AbstractNewEmulatorInstanceWizard could not be used + * for Android Emulator Device Instance New Wizard implementation, so an IInstanceBuilder + * became necessary for our Wizard implementation. + * <br> + * This class should be removed if TmL's Wizard begins to be used since this implementation + * is very similar to TmL DefaultInstanceBuilder implementation and Tml's Wizard would use + * it instead. + * <br> + * RESPONSIBILITY: + * <br> + * - Hold necessary information about a Android Emulator Device Instance for it to be + * created. + * <br> + * COLABORATORS: + * <br> + * IInstanceBuilder: implements this interface + * <br> + * USAGE: + * <br> + * The AndroidNewWizard uses this class to hold information for creating a Android Emulator Device + * Instance and passes it on to TmL's InstanceManager to carry on the creation. + */ +public class AndroidDevInstBuilder implements IInstanceBuilder +{ + private final Properties properties; + + private final String name; + + /** + * Creates a new Instance Builder with the given information. + * + * @param instanceName the name of the instance to be created using this builder + * @param properties the properties of the instance to be created using this builder + */ + public AndroidDevInstBuilder(String instanceName, Properties properties) + { + this.properties = properties; + this.name = instanceName; + } + + /** + * Always returns <code>null</code> since this information does + * not make sense for Android Emulator Instances. + */ + public IPath getLocationPath() + { + return null; + } + + /** + * Retrieves the name of the instance to be created using this builder + * + * @return the name of the instance to be created using this builder + */ + public String getProjectName() + { + return name; + } + + /** + * Retrieves the properties of the instance to be created using this builder + * + * @return the properties of the instance to be created using this builder + */ + public Properties getProperties() + { + return properties; + } + + /** + * Retrieves the value of the give property key. + * + * @param key the key of the property + * + * @return the value for the property for the instance to be created using this builder + */ + public String getProperty(String key) + { + return properties.getProperty(key); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDevInstListener.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDevInstListener.java new file mode 100644 index 0000000..8e9ff9b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDevInstListener.java @@ -0,0 +1,130 @@ +/* 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 org.eclipse.sequoyah.device.framework.DevicePlugin; +import org.eclipse.sequoyah.device.framework.events.IInstanceListener; +import org.eclipse.sequoyah.device.framework.events.InstanceAdapter; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.model.IInstance; + +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.utils.EmulatorCoreUtils; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; + +/** + * DESCRIPTION: + * Implementation of IInstanceListener for device related actions that depend + * on the TmL instance registry state. + * <br> + * RESPONSIBILITY: + * Guarantee that the emulator views are updated + * Run the initialization service when an instance is loaded + * <br> + * COLABORATORS: + * None. + * <br> + * USAGE: + * This class shall be used by Eclipse only. + */ +public class AndroidDevInstListener extends InstanceAdapter +{ + + /** + * @see IInstanceListener#instanceLoaded(InstanceEvent) + */ + @Override + public void instanceLoaded(InstanceEvent e) + { + IInstance instance = e.getInstance(); + + if (instance instanceof IAndroidEmulatorInstance) + { + // The service definition defined (by convention) that + // stopped-dirty is the success state, and not available + // is the failure state. The exception is being thrown for + // the framework to set the state correctly. + if (instance.getStatus().equals(DevicePlugin.SEQUOYAH_STATUS_OFF)) + { + instance.setStatus(EmulatorPlugin.STATUS_NOT_AVAILABLE); + } + } + } + + /** + * @see IInstanceListener#instanceDeleted(InstanceEvent) + */ + @Override + public void instanceDeleted(InstanceEvent ev) + { + IInstance instance = ev.getInstance(); + if (instance instanceof AndroidDeviceInstance) + { + SdkUtils.deleteVm(instance.getName()); + } + } + + /** + * @see IInstanceListener#instanceTransitioned(InstanceEvent) + */ + @Override + public void instanceTransitioned(InstanceEvent e) + { + IInstance instance = e.getInstance(); + + if (instance instanceof AndroidDeviceInstance) + { + final AndroidDeviceInstance androidDevice = (AndroidDeviceInstance) instance; + StudioLogger.info("The android device instance status was updated: " + instance + + " Status: " + instance.getStatus()); + + if (androidDevice.isStarted()) + { + String transitionId = e.getTransitionId(); + if ((transitionId != null) + && transitionId.equals("com.motorola.studio.android.emulator.startService")) + { + // If it is coming from other state than the started, + // connect to VNC server + StudioLogger + .info("The emulator " + + instance + + " transitioned to started state. Try to estabilish a VNC connection..."); + + new Thread(new Runnable() + { + @Override + public void run() + { + AbstractAndroidView.showView(); + EmulatorCoreUtils.refreshEmulatorViews(); + } + }).start(); + } + } + else if (instance.getStatus().equals(EmulatorPlugin.STATUS_OFFLINE)) + { + androidDevice.resetRuntimeVariables(); + EmulatorCoreUtils.refreshEmulatorViews(); + } + + } + + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDeviceInstance.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDeviceInstance.java new file mode 100644 index 0000000..b08f556 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/AndroidDeviceInstance.java @@ -0,0 +1,776 @@ +/*
+* 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;
+ }
+
+}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/IStartupOptionsConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/IStartupOptionsConstants.java new file mode 100644 index 0000000..c82796b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/IStartupOptionsConstants.java @@ -0,0 +1,155 @@ +/* +* 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.options; + +import java.util.HashMap; +import java.util.Map; + +/** + * This interface contains constants used for the Startup Options Management + * + */ +@SuppressWarnings("serial") +public interface IStartupOptionsConstants +{ + + /* + * XML Path + */ + public final String STARTUP_OPTIONS_XML_PATH = "resource/startup_options.xml"; + + /* + * XML tags + */ + public final String ROOT_TAG = "startupOptions"; + + public final String GROUP_TAG = "group"; + + public final String GROUP_TAG_ID = "id"; + + public final String STARTUP_OPT_TAG = "startupOption"; + + public final String STARTUP_OPT_TAG_NAME = "name"; + + public final String STARTUP_OPT_TAG_FRIENDLY_NAME = "fName"; + + public final String STARTUP_OPT_TAG_TYPE = "type"; + + public final String STARTUP_OPT_TAG_TYPE_DETAILS = "typeDetails"; + + public final String STARTUP_OPT_TAG_DESCRIPTION = "description"; + + public final String PREDEFINED_VALUES_TAG = "values"; + + public final String PREDEFINED_VALUE_TAG = "value"; + + /* + * Startup option value type + */ + public final int TYPE_NONE = 0; + + public final int TYPE_TEXT = 1; + + public final int TYPE_PATH = 2; + + public final int TYPE_NUMBER = 3; + + public final String TYPE_PATH_DIR = "dir"; + + public final Map<String, Integer> TYPE_MAP = new HashMap<String, Integer>() + { + { + put("none", TYPE_NONE); + put("text", TYPE_TEXT); + put("path", TYPE_PATH); + put("int", TYPE_NUMBER); + } + + }; + + /* + * Disk images options + */ + public final String DISKIMAGES_GROUP = "Disk Images"; + + public final String DISKIMAGES_CACHE = "-cache"; + + public final String DISKIMAGES_DATA = "-data"; + + public final String DISKIMAGES_IMAGE = "-image"; + + public final String DISKIMAGES_INITDATA = "-initdata"; + + public final String DISKIMAGES_KERNEL = "-kernel"; + + public final String DISKIMAGES_NOCACHE = "-nocache"; + + public final String DISKIMAGES_RAMDISK = "-ramdisk"; + + public final String DISKIMAGES_SDCARD = "-sdcard"; + + public final String DISKIMAGES_SYSTEM = "-system"; + + public final String DISKIMAGES_WIPEDATA = "-wipe-data"; + + /* + * Network options + */ + public final String NETWORK_GROUP = "Network"; + + public final String NETWORK_DNS_SERVER = "-dns-server"; + + public final String NETWORK_HTTP_PROXY = "-http-proxy"; + + public final String NETWORK_NETDELAY = "-netdelay"; + + public final String NETWORK_NETFAST = "-netfast"; + + public final String NETWORK_NETSPEED = "-netspeed"; + + public final String NETWORK_PORT = "-port"; + + /* + * System options + */ + public final String SYSTEM_GROUP = "System"; + + public final String SYSTEM_CPU_DELAY = "-cpu-delay"; + + public final String SYSTEM_GPS = "-gps"; + + public final String SYSTEM_NO_JNI = "-nojni"; + + /* + * UI options + */ + public final String UI_GROUP = "UI"; + + public final String UI_DPI_DEVICE = "-dpi-device"; + + public final String SCALE = "-scale"; + + public final String NO_BOOT_AIM = "-no-boot-anim"; + + public final String NO_SKIN = "-no-skin"; + + /* + * Other options + */ + public final String OTHERS_GROUP = "Others"; + + public final String OTHERS_OTHER = "other"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOption.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOption.java new file mode 100644 index 0000000..12a15c7 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOption.java @@ -0,0 +1,307 @@ +/* +* 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.options; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Widget; + +/** + * Bean that represents an startup option + * + */ +public class StartupOption +{ + + // Checked status (whether the startup options is being used or not) + private boolean checked; + + // Widget that represents the checked status in the UI + private Widget checkedWidget; + + // Startup option name + private String name; + + // Startup option user-friendly name + private String userFriendlyName; + + // Startup option description (user-friendly description) + private String description; + + // Startup option type (which type of values that the startup option accepts) + private int type; + + // Startup option type details (details of the values that the startup option accepts) + private String typeDetails; + + // Startup option value (startup option configured value) + private String value; + + // Widget that represents the startup option value in the UI + private Widget valueWidget; + + // Startup option predefined values (list of values the startup option accepts) + private List<String> preDefinedValues; + + /** + * Constructor + * + * @param name + * @param type + */ + public StartupOption(String name, int type) + { + this.checked = false; + this.name = name; + this.type = type; + this.value = ""; + this.preDefinedValues = new ArrayList<String>(); + } + + /** + * Get startup option name + * + * @return startup option name + */ + public String getName() + { + return name; + } + + /** + * Set startup option name + * + * @param name startup option name + */ + public void setName(String name) + { + this.name = name; + } + + /** + * Get startup option user-friendly name + * + * @return + */ + public String getUserFriendlyName() + { + return userFriendlyName; + } + + /** + * Set startup option user-friendly name + * + * @param userFriendlyName + */ + public void setUserFriendlyName(String userFriendlyName) + { + this.userFriendlyName = userFriendlyName; + } + + /** + * Get startup option type + * + * @return startup option type + */ + public int getType() + { + return type; + } + + /** + * Set startup option type + * + * @param type startup option type + */ + public void setType(int type) + { + this.type = type; + } + + /** + * Get startup option value + * + * @return startup option value + */ + public String getValue() + { + return value; + } + + /** + * Set startup option value + * + * @param value startup option value + */ + public void setValue(String value) + { + this.value = value; + } + + /** + * Get startup option pre-defined values + * + * @return startup option pre-defined values + */ + public List<String> getPreDefinedValues() + { + return preDefinedValues; + } + + /** + * Set startup option pre-defined values + * + * @param preDefinedValues startup option pre-defined values + */ + public void setPreDefinedValues(List<String> preDefinedValues) + { + this.preDefinedValues = preDefinedValues; + } + + /** + * Check if the startup option is being used + * + * @return true if the startup option is being used, false otherwise + */ + public boolean isChecked() + { + return checked; + } + + /** + * Set that the startup option is being used or not + * + * @param checked true if the startup option is being used, false otherwise + */ + public void setChecked(boolean checked) + { + this.checked = checked; + } + + /** + * Get startup option description + * + * @return startup option description + */ + public String getDescription() + { + return description; + } + + /** + * Set startup option description + * + * @param description startup option description + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * Get startup option type details + * + * @return startup option type details + */ + public String getTypeDetails() + { + return typeDetails; + } + + /** + * Get startup option type details + * + * @param typeDetails valuable information for validating if the value assigned is correct + */ + public void setTypeDetails(String typeDetails) + { + this.typeDetails = typeDetails; + } + + /** + * Get the widget that represents the checked status in the UI + * + * @return widget that represents the checked status in the UI + */ + public Widget getCheckedWidget() + { + return checkedWidget; + } + + /** + * Set the widget that represents the checked status in the UI + * + * @param checkedWidget widget that represents the checked status in the UI + */ + public void setCheckedWidget(Widget checkedWidget) + { + this.checkedWidget = checkedWidget; + } + + /** + * Get the widget that represents the startup option value in the UI + * + * @return widget that represents the startup option value in the UI + */ + public Widget getValueWidget() + { + return valueWidget; + } + + /** + * Set the widget that represents the startup option value in the UI + * + * @param valueWidget widget that represents the startup option value in the UI + */ + public void setValueWidget(Widget valueWidget) + { + this.valueWidget = valueWidget; + } + + /** + * Update the widgets that represent this startup options in the UI + * by changing their state to match the current values for checked and value + */ + public void updateUI() + { + if (checkedWidget != null && !checkedWidget.isDisposed()) + { + ((Button) this.checkedWidget).setSelection(this.checked); + } + if (valueWidget != null && !checkedWidget.isDisposed()) + { + if (this.valueWidget instanceof Text) + { + ((Text) this.valueWidget).setText(this.value); + } + else if (this.valueWidget instanceof Combo) + { + if ((this.value == null) || (this.value.equals(""))) + { + ((Combo) this.valueWidget).deselectAll(); + } + else + { + ((Combo) this.valueWidget).select(getPreDefinedValues().indexOf(this.value)); + } + } + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOptionsGroup.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOptionsGroup.java new file mode 100644 index 0000000..1348318 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOptionsGroup.java @@ -0,0 +1,106 @@ +/* +* 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.options; + +import java.util.ArrayList; +import java.util.List; + +/** + * Bean that represents an startup options group + * + */ +public class StartupOptionsGroup +{ + // Group ID + private String id; + + // Group Title (user-friendly title) + private String title; + + // Startup options (list of the startup options in this group) + private List<StartupOption> startupOptions = new ArrayList<StartupOption>(); + + /** + * Constructor + * + * @param id + */ + public StartupOptionsGroup(String id) + { + this.id = id; + } + + /** + * Get startup option group ID + * + * @return startup option group ID + */ + public String getId() + { + return id; + } + + /** + * Set startup option group ID + * + * @param id startup option group ID + */ + public void setId(String id) + { + this.id = id; + } + + /** + * Get the startup options in this group + * + * @return startup options in this group + */ + public List<StartupOption> getStartupOptions() + { + return startupOptions; + } + + /** + * Set the startup options in this group + * + * @param startupOptions startup options in this group + */ + public void setStartupOptions(List<StartupOption> startupOptions) + { + this.startupOptions = startupOptions; + } + + /** + * Get startup option group title + * + * @return startup option group title + */ + public String getTitle() + { + return title; + } + + /** + * Set startup option group title + * + * @param title startup option group title + */ + public void setTitle(String title) + { + this.title = title; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOptionsMgt.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOptionsMgt.java new file mode 100644 index 0000000..bd5710d --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/instance/options/StartupOptionsMgt.java @@ -0,0 +1,785 @@ +/* +* 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.options; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.osgi.util.NLS; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * This class provides methods to manage startup options + * + */ +public class StartupOptionsMgt implements IStartupOptionsConstants +{ + + /** + * List of all startup options groups (the startup options themselves will + * be accessed through the use of a method in the group object that returns + * the startup options in that group) + */ + private static List<StartupOptionsGroup> startupOptionsGroupsList = null; + + /** + * List of all startup options, indexed by their names, for fast access + */ + private static Map<String, StartupOption> startupOptionsMap = + new HashMap<String, StartupOption>(); + + /* + * Load the startup options / groups list + */ + static + { + load(); + } + + /** + * Get the startup options groups list + * + * @return startup options groups list + */ + public static List<StartupOptionsGroup> getStartupOptionsGroupsList() + { + return startupOptionsGroupsList; + } + + /** + * Read all groups and startup options available for editing from a XML + * and stores the information in the correspondent beans + */ + public static void load() + { + + try + { + // Clear startup options groups list + startupOptionsGroupsList = new ArrayList<StartupOptionsGroup>(); + + // Define XML path + InputStream xmlStream = + EmulatorPlugin.getDefault().getBundle().getEntry(STARTUP_OPTIONS_XML_PATH) + .openStream(); + + // Load XML + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(xmlStream); + + /* + * Iterate through Startup Groups + */ + Element rootNode = document.getDocumentElement(); + NodeList startupOptionsGroups = rootNode.getElementsByTagName(GROUP_TAG); + for (int i = 0; i < startupOptionsGroups.getLength(); i++) + { + /* + * Create group + */ + Element group = (Element) startupOptionsGroups.item(i); + + String strKey = group.getAttributeNode(GROUP_TAG_ID).getNodeValue(); + strKey = + Platform.getResourceString(EmulatorPlugin.getDefault().getBundle(), strKey); + + StartupOptionsGroup startupOptionsGroup = new StartupOptionsGroup(strKey); + startupOptionsGroup.setTitle(startupOptionsGroup.getId()); + + /* + * Iterate through Startup Options in this group + */ + NodeList startupOptions = group.getElementsByTagName(STARTUP_OPT_TAG); + startupOptionsGroup.setStartupOptions(new ArrayList<StartupOption>()); // clear startup options + for (int j = 0; j < startupOptions.getLength(); j++) + { + /* + * Create startup option + */ + Element option = (Element) startupOptions.item(j); + StartupOption startupOption = + new StartupOption(option.getAttributeNode(STARTUP_OPT_TAG_NAME) + .getNodeValue(), getStartupOptionType(option.getAttributeNode( + STARTUP_OPT_TAG_TYPE).getNodeValue())); // name and type + strKey = option.getAttributeNode(STARTUP_OPT_TAG_FRIENDLY_NAME).getNodeValue(); + + strKey = + Platform.getResourceString(EmulatorPlugin.getDefault().getBundle(), + strKey); + + startupOption.setUserFriendlyName(strKey); // friendly name + + strKey = + option.getElementsByTagName(STARTUP_OPT_TAG_DESCRIPTION).item(0) + .getTextContent(); + + strKey = + Platform.getResourceString(EmulatorPlugin.getDefault().getBundle(), + strKey); + + startupOption.setDescription(strKey); // description + + if (option.getAttributeNode(STARTUP_OPT_TAG_TYPE_DETAILS) != null) + { + startupOption.setTypeDetails(option.getAttributeNode( + STARTUP_OPT_TAG_TYPE_DETAILS).getNodeValue()); // type details + } + // Iterate through startup option pre-defined values, if any + NodeList preDefinedValuesContainer = + option.getElementsByTagName(PREDEFINED_VALUES_TAG); + startupOption.setPreDefinedValues(new ArrayList<String>()); // clear pre-defined values + if (preDefinedValuesContainer.getLength() > 0) + { + NodeList preDefinedValues = + ((Element) preDefinedValuesContainer.item(0)) + .getElementsByTagName(PREDEFINED_VALUE_TAG); + for (int k = 0; k < preDefinedValues.getLength(); k++) + { + // Add pre-defined values to the option + Element preDefinedValue = (Element) preDefinedValues.item(k); + startupOption.getPreDefinedValues().add( + preDefinedValue.getTextContent()); + } + } + + /* + * Add startup options to the group + */ + startupOptionsGroup.getStartupOptions().add(startupOption); + + startupOptionsMap.put(startupOption.getName(), startupOption); + + } + + /* + * Add groups to the groups list + */ + startupOptionsGroupsList.add(startupOptionsGroup); + } + + } + catch (Exception e) + { + StudioLogger.error("Failed to load startup options"); + } + + } + + /** + * Validate the values assigned for the startup options marked as checked (the ones that are + * being used), according to its type and type details. + * + * @return Status object with the result of the validation + */ + public static Status validate() + { + Status status = (Status) Status.OK_STATUS; + String msg = null; + + /* + * Iterate through Startup Groups + */ + for (StartupOptionsGroup group : getStartupOptionsGroupsList()) + { + /* + * Iterate through Startup Options in this group + */ + for (StartupOption startupOption : group.getStartupOptions()) + { + /* + * Check if the Startup Option is checked + */ + if (startupOption.isChecked() && (status.isOK())) + { + + String name = startupOption.getName(); // startup option name + String ufname = startupOption.getUserFriendlyName(); // user-friendly startup option name + String value = startupOption.getValue(); // startup option value + String typeDetails = startupOption.getTypeDetails(); // startup option type detail + + /* + * General validation: no quotes in values + */ + if ((!startupOption.getName().equals(OTHERS_OTHER)) + && (value.indexOf("\"") >= 0)) + { + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_NoQuotes, + ufname); + } + else + { + + /* + * Call the appropriate validation method + */ + switch (startupOption.getType()) + { + case TYPE_TEXT: + msg = validateTextField(name, ufname, value, typeDetails); + break; + + case TYPE_NUMBER: + msg = validadeNumberField(name, ufname, value, typeDetails); + break; + + case TYPE_PATH: + msg = validadePathField(name, ufname, value, typeDetails); + break; + } + } + + /* + * If some validation has failed, return with an error message + */ + if (msg != null) + { + status = new Status(Status.ERROR, EmulatorPlugin.PLUGIN_ID, msg); + break; + } + + } + } + } + + return status; + + } + + /** + * Validate the startup option value for an startup option of "text" type + * + * @param name the startup option name + * @param ufname the user-friendly startup option name + * @param value the current assigned value for the startup option + * @param typeDetails any special requirements that the assigned value must be match + * @return null if the value assigned for the startup option is a valid one or an error message otherwise + */ + private static String validateTextField(String name, String ufName, String value, + String typeDetails) + { + String msg = null; + + // Check if the value is blank + if ((value == null) || (value.equals(""))) + { + msg = NLS.bind(EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_TextBlank, ufName); + } + + return msg; + + } + + /** + * Validate the startup option value for an startup option of "number" type + * + * @param name the startup option name + * @param ufname the user-friendly startup option name + * @param value the current assigned value for the startup option + * @param typeDetails any special requirements that the assigned value must be match + * @return null if the value assigned for the startup option is a valid one or an error message otherwise + */ + private static String validadeNumberField(String name, String ufName, String value, + String typeDetails) + { + String msg = null; + + // Check if the value is blank + if ((value == null) || (value.equals(""))) + { + msg = + NLS.bind(EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_NumberRequired, + ufName); + } + else + { + + try + { + /* + * Check if it's an Integer. + * If it's not, an exception will be thrown + */ + int intValue = Integer.parseInt(value); + + /* + * Check if it's positive + */ + if (intValue < 0) + { + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_NumberMustBePositiveInteger, + ufName); + } + else + { + + /* + * Check if the value is in the correct range + */ + if (typeDetails != null) + { + String[] valueRange = typeDetails.split(";"); + if ((intValue < Integer.parseInt(valueRange[0])) + || (intValue > Integer.parseInt(valueRange[1]))) + { + // the value is not in the correct range + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_NumberIntRange, + new String[] + { + ufName, valueRange[0], valueRange[1] + }); + } + } + } + + } + catch (NumberFormatException ex) + { + // it's not a number + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_NumberMustBeInteger, + ufName); + } + } + + return msg; + + } + + /** + * Validate the startup option value for an startup option of "path" type + * + * @param name the startup option name + * @param ufname the user-friendly startup option name + * @param value the current assigned value for the startup option + * @param typeDetails any special requirements that the assigned value must be match + * @return null if the value assigned for the startup option is a valid one or an error message otherwise + */ + private static String validadePathField(String name, String ufName, String value, + String typeDetails) + { + String msg = null; + + if ((value == null) || (value.equals(""))) + { + msg = NLS.bind(EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_PathRequired, ufName); + } + else + { + + File file = new File(value); + + /* + * Validate folder + */ + if (typeDetails.equals(TYPE_PATH_DIR)) + { + /* + * Check if the path exists + */ + if (!file.exists()) + { + // the folder doesn't exist + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_PathDirNotExist, + ufName); + } + else + { + if (file.isFile()) + { + // it's not a folder + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_PathMustBeDir, + ufName); + } + } + } + /* + * Validate file + */ + else + { + if (!file.exists()) + { + // the file doesn't exist + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_PathFileNotExist, + ufName); + } + else + { + // it's not a file + if (file.isDirectory()) + { + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_PathMustBeFile, + ufName); + } + // it doesn't have the correct extension + else + { + if (!typeDetails.equals("." + (new Path(value)).getFileExtension())) + { + msg = + NLS.bind( + EmulatorNLS.ERR_PropertiesMainComposite_StartupOpt_PathIncorrectFileType, + new String[] + { + ufName, typeDetails + }); + } + } + } + } + } + return msg; + + } + + /** + * Generates the list of parameters that shall to be sent to the Emulator + * + * @return the list of parameters that shall to be sent to the Emulator + */ + public static String getParamList() + { + String paramList = ""; + + /* + * Iterate through Startup Groups + */ + for (StartupOptionsGroup group : getStartupOptionsGroupsList()) + { + /* + * Iterate through Startup Options in this group + */ + int startupOptionType; + for (StartupOption startupOption : group.getStartupOptions()) + { + startupOptionType = startupOption.getType(); + if (startupOption.isChecked()) // check if the startup option is being used + { + if (startupOptionType == TYPE_NONE) + { + paramList += ((paramList.equals("")) ? "" : " ") + startupOption.getName(); + } + else + { + if ((startupOption.getName().equals(OTHERS_OTHER))) + { + + paramList += + ((paramList.equals("")) ? "" : " ") + startupOption.getValue(); + + } + else + { + String value = startupOption.getValue(); + + if (Platform.getOS().equals(Platform.OS_WIN32)) + { + if (value.contains(" ")) + { + value = "\"" + value + "\""; + } + } + else + { + if (value.contains("\\")) + { + value = value.replace("\\", "\\\\"); + } + + if (value.contains(" ")) + { + value = value.replace(" ", "\\ "); + } + } + + paramList += + ((paramList.equals("")) ? "" : " ") + startupOption.getName() + + (value.trim().length() > 0 ? " " + value : ""); + } + } + } + } + } + + return paramList; + + } + + /** + * Load values from a Properties object + * + * @param properties properties object containing the values that must be loaded into de model + */ + private static void loadValues(Properties properties) + { + /* + * Iterate through Startup Groups + */ + for (StartupOptionsGroup group : getStartupOptionsGroupsList()) + { + /* + * Iterate through Startup Options in this group + */ + String soValue = ""; + for (StartupOption startupOption : group.getStartupOptions()) + { + soValue = properties.getProperty(startupOption.getName()); + if (soValue != null) + { + startupOption.setChecked(true); + startupOption.setValue(soValue); + } + else + { + startupOption.setChecked(false); + startupOption.setValue(""); + } + startupOption.updateUI(); + } + } + + } + + /** + * Create a properties object with the information contained in a command line + * + * @param commandLine the command line used to start the emulator + * @return properties object with the information contained in a command line + */ + public static Properties parseCommandLine(String commandLine) + { + Properties properties = new Properties(); + + if (!commandLine.equals("")) + { + + /* + * Iterate through Startup Groups + */ + for (StartupOptionsGroup group : getStartupOptionsGroupsList()) + { + /* + * Iterate through Startup Options in this group + */ + String soName, soValue = ""; + int soType, shift = 0; + for (StartupOption startupOption : group.getStartupOptions()) + { + soName = startupOption.getName(); + soType = startupOption.getType(); + if (commandLine.startsWith(soName)) + { + if (soType == TYPE_NONE) + { + soValue = new Boolean(true).toString(); + shift = soName.length() + 1; + } + else + { + commandLine = + commandLine + .substring(soName.length() + 1, commandLine.length()); + //int endValueIndex = commandLine.indexOf("\""); + ParameterBean param = getNextParameterValue(commandLine); + soValue = param.getValue(); + shift = param.getLastPosition() + 1; + } + + properties.put(startupOption.getName(), soValue); + + if (shift < commandLine.length() - 1) + { + commandLine = commandLine.substring(shift, commandLine.length()); + } + else + { + commandLine = ""; + } + } + } + } + + if (!commandLine.equals("")) + { + properties.put(OTHERS_OTHER, commandLine); + } + } + + return properties; + + } + + /** + * Load values from a command line + * + * @param commandLine the command line used to start the emulator + */ + public static void loadFromCommandLine(String commandLine) + { + loadValues(parseCommandLine(commandLine)); + } + + /** + * Convert the type of the startup option from a string + * to a number + * + * @param type string that represents the type + * @return number that represents the type + */ + private static int getStartupOptionType(String type) + { + return (TYPE_MAP.get(type)).intValue(); + } + + /** + * Parses the next parameter value on a command line + * + * @param commandLine the command line + * + * @return a bean containing the parameter value and the last position looked at + * the command line + */ + private static ParameterBean getNextParameterValue(String commandLine) + { + boolean isWin32 = Platform.getOS().equals(Platform.OS_WIN32); + boolean escaped = false; + boolean quoted = false; + + char c; + String value = ""; + int i; + + for (i = 0; i < commandLine.length(); i++) + { + c = commandLine.charAt(i); + + if (escaped) + { + value += c; + escaped = false; + } + else if (c == '\\' && !isWin32) + { + escaped = true; + } + else if (c == '"' && isWin32) + { + if (value.length() == 0) + { + quoted = true; + } + else if (quoted) + { + break; + } + else + { + value += c; + } + } + else if ((c == ' ') && (!quoted)) + { + break; + } + else + { + value += c; + } + } + + return new ParameterBean(value, ((quoted) ? i + 1 : i)); + } + + /** + * Bean used to identify a parameter value when parsing the + * startup options + * + */ + private static class ParameterBean + { + private final String value; + + private final int lastPosition; + + /** + * Constructor + * + * @param value The parameter value + * @param lastPosition The last position looked at the command line before stopping + * the parse operation + */ + public ParameterBean(String value, int lastPosition) + { + this.value = value; + this.lastPosition = lastPosition; + } + + /** + * Retrieves the parameter value + * + * @return the parameter value + */ + public String getValue() + { + return value; + } + + /** + * Retrieves the last position looked at the command line before stopping + * the parse operation + * + * @return the last position looked at the command line before stopping + * the parse operation + */ + public int getLastPosition() + { + return lastPosition; + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/refresh/InstancesListRefresh.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/refresh/InstancesListRefresh.java new file mode 100644 index 0000000..23295e9 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/refresh/InstancesListRefresh.java @@ -0,0 +1,184 @@ +/*
+* 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.refresh;
+
+import static com.motorola.studio.android.common.log.StudioLogger.error;
+import static com.motorola.studio.android.common.log.StudioLogger.info;
+
+import java.util.Collection;
+import java.util.Properties;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.sequoyah.device.common.utilities.exception.SequoyahException;
+import org.eclipse.sequoyah.device.framework.DevicePlugin;
+import org.eclipse.sequoyah.device.framework.factory.DeviceTypeRegistry;
+import org.eclipse.sequoyah.device.framework.manager.InstanceManager;
+import org.eclipse.sequoyah.device.framework.model.IDeviceType;
+
+import com.motorola.studio.android.adt.SdkUtils;
+import com.motorola.studio.android.emulator.EmulatorPlugin;
+import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager;
+import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance;
+import com.motorola.studio.android.emulator.device.AndroidDeviceUtils;
+import com.motorola.studio.android.emulator.device.instance.AndroidDevInstBuilder;
+import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance;
+
+/**
+ * This class is responsible for refreshing the TML Instances List It checks if
+ * the user has created more Android VMs by himself
+ *
+ */
+public class InstancesListRefresh
+{
+
+ /**
+ * If the number of Android VMs is different from the number of TML
+ * Instances, the TML Instances list is updated
+ */
+ public static synchronized void refresh()
+ {
+
+ SdkUtils.reloadAvds();
+
+ DeviceFrameworkManager devFramework = DeviceFrameworkManager.getInstance();
+
+ if (SdkUtils.getCurrentSdk() != null)
+ {
+ final Collection<String> vmInstances = SdkUtils.getAllVmNames();
+ final Collection<String> validVmInstances = SdkUtils.getAllValidVmNames();
+ final Collection<String> emulatorInstances = devFramework.getAllInstanceNames();
+
+ createAndUpdateEmulatorInstances(vmInstances, validVmInstances, emulatorInstances);
+ }
+ }
+
+ /**
+ * Creates Emulator instances to represent every VM available in the system.
+ * @param validVmInstances
+ **/
+ public static void createAndUpdateEmulatorInstances(Collection<String> vmInstances,
+ Collection<String> validVmInstances,
+ Collection<String> emulatorInstances)
+ {
+
+ IDeviceType device =
+ DeviceTypeRegistry.getInstance().getDeviceTypeById(EmulatorPlugin.DEVICE_ID);
+
+ for (String instanceName : vmInstances)
+ {
+ /*
+ * In case the is no TmL instances for a given VM, create the TmL
+ * Instance
+ */
+ if (!emulatorInstances.contains(instanceName))
+ {
+
+ Properties instanceProperties = new Properties();
+
+ AndroidDeviceInstance.populateWithVMInfo(instanceName, instanceProperties);
+
+ AndroidDeviceInstance.populateWithDefaultProperties(instanceProperties);
+
+ AndroidDevInstBuilder projectBuilder =
+ new AndroidDevInstBuilder(instanceName, instanceProperties);
+
+ try
+ {
+ InstanceManager
+ .createProject(device, projectBuilder, new NullProgressMonitor());
+ }
+ catch (SequoyahException e)
+ {
+ error("There was an error while creating an emulator instance: " + instanceName
+ + ". Message: " + e.getMessage());
+ }
+
+ refreshStatus(validVmInstances, instanceName);
+
+ info("Added instance " + instanceName + " using default emulator definitions ");
+ }
+
+ }
+
+ for (String emulatorInstance : emulatorInstances)
+ {
+ refreshStatus(validVmInstances, emulatorInstance);
+ }
+
+ }
+
+ public static void refreshStatus(Collection<String> vmInstances, String instanceName)
+ {
+ /*
+ * Refresh status
+ */
+ IAndroidEmulatorInstance instance =
+ DeviceFrameworkManager.getInstance().getInstanceByName(instanceName);
+
+ AndroidDeviceInstance androidDeviceInstance = (AndroidDeviceInstance) instance;
+
+ AndroidDeviceInstance.populateWithVMInfo(androidDeviceInstance.getName(),
+ androidDeviceInstance.getProperties());
+
+ String currentStatus = androidDeviceInstance.getStatus();
+
+ if (androidDeviceInstance.hasDevice())
+ {
+ if ((androidDeviceInstance.getStatus().equals(EmulatorPlugin.STATUS_NOT_AVAILABLE))
+ || (androidDeviceInstance.getStatus().equals(DevicePlugin.SEQUOYAH_STATUS_OFF)))
+ {
+ if (!EmulatorPlugin.STATUS_OFFLINE_NO_DATA.equals(currentStatus))
+ {
+ androidDeviceInstance.setNameSuffix(null);
+ androidDeviceInstance.setStatus(EmulatorPlugin.STATUS_OFFLINE_NO_DATA);
+ }
+ }
+ AndroidDeviceUtils.fireDummyStartTransition(androidDeviceInstance,
+ androidDeviceInstance.getSerialNumber());
+ }
+ else
+ {
+
+ if (vmInstances.contains(androidDeviceInstance.getName()))
+ {
+ if (androidDeviceInstance.isClean())
+ {
+ if (!EmulatorPlugin.STATUS_OFFLINE_NO_DATA.equals(currentStatus))
+ {
+ androidDeviceInstance.setNameSuffix(null);
+ androidDeviceInstance.setStatus(EmulatorPlugin.STATUS_OFFLINE_NO_DATA);
+ }
+ }
+ else
+ {
+ if (!EmulatorPlugin.STATUS_OFFLINE.equals(currentStatus))
+ {
+ androidDeviceInstance.setNameSuffix(null);
+ androidDeviceInstance.setStatus(EmulatorPlugin.STATUS_OFFLINE);
+ }
+ }
+ }
+ else
+ {
+ if (!EmulatorPlugin.STATUS_NOT_AVAILABLE.equals(currentStatus))
+ {
+ androidDeviceInstance.setNameSuffix(null);
+ androidDeviceInstance.setStatus(EmulatorPlugin.STATUS_NOT_AVAILABLE);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/refresh/InstancesListRefreshHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/refresh/InstancesListRefreshHandler.java new file mode 100644 index 0000000..3d68c4b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/refresh/InstancesListRefreshHandler.java @@ -0,0 +1,39 @@ +/* +* 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.refresh; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; + +/** + * Class responsible for handling refresh actions + * + */ +public class InstancesListRefreshHandler extends AbstractHandler +{ + + /** + * Call the method responsible for refreshing the TML Instances List + * + * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent) + */ + public Object execute(ExecutionEvent arg0) throws ExecutionException + { + InstancesListRefresh.refresh(); + return null; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/sync/DeviceViewsSync.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/sync/DeviceViewsSync.java new file mode 100644 index 0000000..ec7c49e --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/sync/DeviceViewsSync.java @@ -0,0 +1,326 @@ +/* +* 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.sync; + +import java.lang.reflect.Method; +import java.util.List; + +import org.eclipse.sequoyah.device.framework.factory.InstanceRegistry; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.ui.view.InstanceMgtView; +import org.eclipse.sequoyah.device.framework.ui.view.model.InstanceSelectionChangeEvent; +import org.eclipse.sequoyah.device.framework.ui.view.model.InstanceSelectionChangeListener; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.ide.eclipse.ddms.DdmsPlugin; +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; + +public class DeviceViewsSync +{ + + /** + * DeviceViewsSync unique instance + */ + private static DeviceViewsSync instance = null; + + /** + * Views + */ + public static final int EMULATOR_VIEW = 0; // Emulator View + + public static final int DEVICE_VIEW = 1; // Device Management View + + public static final int DDMS_VIEW = 2; // DDMS Device View + + /** + * Methods used to update the Views + */ + private Method[] syncMethods = null; + + /** + * During the synchronization, it stores the instance + * that shall be set to avoid loops + */ + private static String syncInstance = null; + + /** + * Singleton + * + * @return DeviceViewsSync + */ + public static DeviceViewsSync getInstance() + { + if (instance == null) + { + instance = new DeviceViewsSync(); + } + return instance; + } + + /* + * Constructor + * + * Define the synchronization methods + * Define the methods that retrieve the current selection in a View + */ + @SuppressWarnings("rawtypes") + private DeviceViewsSync() + { + + try + { + + /* + * Register methods that update each view + */ + Class parameterTypes[] = new Class[1]; + parameterTypes[0] = String.class; + + syncMethods = new Method[3]; + + syncMethods[EMULATOR_VIEW] = + this.getClass().getDeclaredMethod("syncEmulatorView", parameterTypes); + syncMethods[DEVICE_VIEW] = + this.getClass().getDeclaredMethod("syncDeviceView", parameterTypes); + syncMethods[DDMS_VIEW] = + this.getClass().getDeclaredMethod("syncDDMSView", parameterTypes); + } + catch (Exception e) + { + StudioLogger.error("Could not add syncronization method: " + e.getMessage()); + } + + } + + /** + * Add listeners to events that must initiate the synchronization procedures + * + * #1) Emulator View + * + * #2) Device Management View + * + * #3) DDMS Device View + */ + public void initialize() + { + + /* + * Synchronization #1 + * Add listener to Emulator View tab switch event + */ + AbstractAndroidView.addTabSwitchListener(new Listener() + { + @Override + public void handleEvent(Event event) + { + + IAndroidEmulatorInstance activeInstance = AbstractAndroidView.getActiveInstance(); + if (activeInstance != null) + { + String selectedInstanceName = activeInstance.getName(); + if ((selectedInstanceName != null) + && (!selectedInstanceName.equals(syncInstance))) + { + sync(EMULATOR_VIEW, selectedInstanceName); + } + } + + } + }); + + /* + * Synchronization #2 + */ + InstanceMgtView.addInstanceSelectionChangeListener(new InstanceSelectionChangeListener() + { + @Override + public void instanceSelectionChanged(InstanceSelectionChangeEvent event) + { + + IInstance instance = event.getInstance(); + if ((instance != null) + && (EmulatorPlugin.STATUS_ONLINE_ID.equals(instance.getStatus()))) + { + String selectedInstanceName = instance.getName(); + if ((selectedInstanceName != null) + && (!selectedInstanceName.equals(syncInstance))) + { + sync(DEVICE_VIEW, selectedInstanceName); + } + } + + } + }); + + /* + * Synchronization #3 + */ + DdmsPlugin.getDefault().addSelectionListener(new DdmsPlugin.ISelectionListener() + { + @Override + public void selectionChanged(Client client) + { + // none + } + + @Override + public void selectionChanged(IDevice device) + { + + if (device != null) + { + String selectedInstanceName = device.getAvdName(); + if ((selectedInstanceName != null) + && (!selectedInstanceName.equals(syncInstance))) + { + sync(DDMS_VIEW, selectedInstanceName); + } + } + + } + }); + } + + /* + * Run the synchronization procedures + * + * @param fireSyncView the View that has been changed and requires others to synchronize + * @param instanceName the Device Instance name + */ + private void sync(Integer fireSyncView, String instanceName) + { + syncInstance = instanceName; + + Object arglist[] = new Object[1]; + arglist[0] = instanceName; + + for (int i = 0; i < syncMethods.length; i++) + { + if (i != fireSyncView) + { + try + { + syncMethods[i].invoke(this, arglist); + } + catch (Exception e) + { + StudioLogger.error("Could not call syncronization method for " + i + " : " + + e.getMessage()); + } + } + } + + syncInstance = null; + + } + + /* + * Synchronize the Emulator View by setting the selected instance + * + * @param instanceName the Device Instance name + */ + @SuppressWarnings("unused") + private void syncEmulatorView(String instanceName) + { + try + { + IAndroidEmulatorInstance emulatorInstance = + DeviceFrameworkManager.getInstance().getInstanceByName(instanceName); + if (emulatorInstance != null) + { + AbstractAndroidView.setInstance(emulatorInstance); + } + else + { + StudioLogger.warn("Could not synchronize with Emulator View: " + instanceName + + " not in DeviceFrameworkManager model"); + } + + } + catch (Exception e) + { + StudioLogger.error("Could not synchronize with Emulator View: " + e.getMessage()); + } + } + + /* + * Synchronize the Device Management View by setting the selected instance + * + * @param instanceName the Device Instance name + */ + @SuppressWarnings("unused") + private void syncDeviceView(String instanceName) + { + + try + { + InstanceRegistry registry = InstanceRegistry.getInstance(); + List<IInstance> tmlInstances = registry.getInstancesByName(instanceName); + if (tmlInstances.size() > 0) + { + IInstance tmlInstance = tmlInstances.get(0); + InstanceMgtView.setSeletectedInstance(tmlInstance); + } + else + { + StudioLogger.warn("Could not synchronize with Device Management View: " + + instanceName + " not in TmL InstanceManager model"); + } + } + catch (Exception e) + { + StudioLogger.error("Could not synchronize with Device Management View: " + + e.getMessage()); + } + + } + + /* + * Synchronize the DDMS Device View by setting the selected instance + * + * @param instanceName the Device Instance name + */ + @SuppressWarnings("unused") + private void syncDDMSView(String instanceName) + { + try + { + IDevice device = DDMSFacade.getDeviceWithVmName(instanceName); + if (device != null) + { + DdmsPlugin.getDefault().selectionChanged(device, null); + } + else + { + StudioLogger + .warn("Could not synchronize with DDMS Devices View: Could not retrieve Device object from ADT model"); + } + } + catch (Exception e) + { + StudioLogger.error("Could not synchronize with DDMS Devices View: " + e.getMessage()); + } + + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AbstractPropertiesComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AbstractPropertiesComposite.java new file mode 100644 index 0000000..8909b19 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AbstractPropertiesComposite.java @@ -0,0 +1,249 @@ +/* +* 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.ui; + +import java.util.Collection; +import java.util.EventListener; +import java.util.EventObject; +import java.util.LinkedHashSet; + +import org.eclipse.sequoyah.device.framework.events.IInstanceListener; +import org.eclipse.sequoyah.device.framework.events.InstanceAdapter; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +/** + * DESCRIPTION: + * <br> + * This class is an abstract implementation of a Composite extension that specific composites + * for making Android Emulator Device Instance UI may use. + * <br> + * It provides common functionalities to those subclasses which assist content validation, + * layout, etc. + * <br> + * RESPONSIBILITY: + * <br> + * - Provide common functionalities to classes implementing Android Emulator Device Instance UI + * <br> + * COLABORATORS: + * <br> + * Composite: extends this class + * <br> + * MagxPropertyCompositeChangeListener: declares this interface for other classes to be able to + * listen to change events on the content + * <br> + * MagxPropertyCompositeChangeEvent: declares the class and uses this kind of event for + * notifying content change to listeners + * <br> + * USAGE: + * <br> + * This class should be extended by UI classes implementing Android Emulator Device Instance + * UI (property pages, wizards, etc). + */ +public abstract class AbstractPropertiesComposite extends Composite +{ + /** + * + * This class represents events for notifying content change on AbstractPropertiesComposite + * extending classes to registered listeners. + * + */ + @SuppressWarnings("serial") + public class PropertyCompositeChangeEvent extends EventObject + { + /** + * Creates a new MagxPropertyCompositeChangeEvent object with the composite + * that changed as its data. + * + * @param composite the composite that changed + */ + PropertyCompositeChangeEvent(AbstractPropertiesComposite composite) + { + super(composite); + } + } + + /** + * + * This interface must be implemented by classes that wish to listen to + * changes on AbstractPropertiesComposite extending classes. + * + */ + public interface PropertyCompositeChangeListener extends EventListener + { + /** + * Notifies the event of change on AbstractPropertiesComposite extending classes. + * + * @param e the change event + */ + public void compositeChanged(PropertyCompositeChangeEvent e); + } + + private class PropertyInstanceListener extends InstanceAdapter + { + private Collection<Control> affectedControls; + + public PropertyInstanceListener(Collection<Control> affectedControls) + { + this.affectedControls = affectedControls; + } + + @Override + public void instanceUpdated(final InstanceEvent e) + { + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + IInstance instance = e.getInstance(); + boolean editable; + if (instance.getStatus().equals(EmulatorPlugin.STATUS_ONLINE_ID)) + { + editable = false; + } + else + { + editable = true; + } + updateWidgetEnableStatus(editable, affectedControls); + } + }); + } + } + + // The default value to use for margins, both vertical and horizontal + private static final int DEFAULT_MARGIN_SIZE = 20; + + // collection of registered listeners to this composite + private static final Collection<PropertyCompositeChangeListener> listeners = + new LinkedHashSet<PropertyCompositeChangeListener>(); + + // The listener that guarantees that if a instance is started it cannot be + // edited by the tool + private IInstanceListener instanceListener; + + public AbstractPropertiesComposite(Composite parent) + { + super(parent, SWT.NONE); + } + + /** + * Adds the given listener to the list of registered listeners of this composite's changes. + * + * @param listener the listener to be registered + */ + public static void addCompositeChangeListener(PropertyCompositeChangeListener listener) + { + listeners.add(listener); + } + + /** + * Removes the given listener from the list of registered listeners of this composite's changes. + * + * @param listener the listener to be unregistered + */ + public static void removeCompositeChangeListener(PropertyCompositeChangeListener listener) + { + listeners.remove(listener); + } + + /** + * Notifies an event of change on the composite to all registered listeners. + * + * This method must be called by extending classes whenever a change is made to them + * that registered listeners should be aware of (examples: typed text, button press, etc) + */ + protected void notifyCompositeChangeListeners() + { + PropertyCompositeChangeEvent event = new PropertyCompositeChangeEvent(this); + + for (PropertyCompositeChangeListener listener : listeners) + { + listener.compositeChanged(event); + } + } + + protected void addInstanceListener(Collection<Control> affectedControls) + { + instanceListener = new PropertyInstanceListener(affectedControls); + InstanceEventManager.getInstance().addInstanceListener(instanceListener); + } + + /** + * Given a collection of controls, turn all their enabled status to + * the one provided + * + * @param enabled True to enable all controls in the collection + * @param controlsToUpdate A collection of all controls to apply the state provided by enabled parameter + */ + protected void updateWidgetEnableStatus(boolean enabled, Collection<Control> controlsToUpdate) + { + for (Control control : controlsToUpdate) + { + if (!control.isDisposed()) + { + control.setEnabled(enabled); + } + } + } + + /** + * Sets the main layout to this composite as a GridLayout with the + * given number of columns and with columns not the same width. + * + * @param numColumns the number of columns for the GridLayout + */ + protected final void setMainLayout(int numColumns) + { + GridLayout mainLayout = new GridLayout(numColumns, false); + mainLayout.marginWidth = DEFAULT_MARGIN_SIZE; + mainLayout.marginHeight = DEFAULT_MARGIN_SIZE; + this.setLayout(mainLayout); + } + + /** + * Declaration of the abstract getErrorMessage() method. + * Retrieves the error message associated with the current extending composite + * state. + * If no error is found with the current state, <code>null</code> <b>must</b> + * be returned by the extending composite. + * + * @return the error message, or <code>null</code> if there are no errors + */ + public abstract String getErrorMessage(); + + /** + * @see Widget#dispose() + */ + @Override + public void dispose() + { + if (instanceListener != null) + { + InstanceEventManager.getInstance().removeInstanceListener(instanceListener); + } + super.dispose(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AndroidPropertiesPage.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AndroidPropertiesPage.java new file mode 100644 index 0000000..83571e0 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AndroidPropertiesPage.java @@ -0,0 +1,185 @@ +/* +* 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.ui; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.preference.PreferenceDialog; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent.InstanceEventType; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbenchPropertyPage; +import org.eclipse.ui.dialogs.PropertyPage; + +import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeEvent; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeListener; + +/** + * DESCRIPTION: + * <br> + * This class implements the Property Page for Android Emulator Device Instances. + * <br> + * It shows all Android Emulator Device Instance properties on the UI so that the user + * is able to edit it (the instance name is not a property and will not be editable). + * <br> + * RESPONSIBILITY: + * <br> + * - Allow viewing and editing of Android Emulator Device Instance properties + * <br> + * COLABORATORS: + * <br> + * PropertyPage: extends this class + * <br> + * InfoComposite: uses this composite for exhibiting instance properties on the UI + * <br> + * USAGE: + * <br> + * This class should be defined by the plugin.xml file as a regular Eclipse Property Page. + * It should be enabled for AndroidEmulatorInstance objects. + */ +public class AndroidPropertiesPage extends PropertyPage implements IWorkbenchPropertyPage, + IDevicePropertiesConstants +{ + // the Android Emulator Device Instance to which this Property Page applies + private AndroidDeviceInstance emuInstance; + + private InfoComposite infoComposite; + + // whether this property page will need its default message to be reset + // this happens in case the initial state of the property page when it is + // opened is an erroneous state (any of the properties contain invalid value) + private boolean defaultMessageNeedsReset = false; + + // the default message defined by Eclipse implementation for reset purposes + private String defaultMessage = getMessage(); + + // handle changes + private PropertyCompositeChangeListener compositeChangeListener = + new PropertyCompositeChangeListener() + { + public void compositeChanged(PropertyCompositeChangeEvent e) + { + String errorMessage = infoComposite.getErrorMessage(); + setErrorMessage(errorMessage); + setValid((errorMessage == null) && (getMessage() == null)); + + if (defaultMessageNeedsReset) + { + defaultMessageNeedsReset = false; + setMessage(defaultMessage); + } + } + }; + + /** + * Creates the UI contents of this Property Page. + * It shows the Android Emulator Device Instance properties + * organized into tabs. + */ + @Override + protected Control createContents(Composite parent) + { + ((PreferenceDialog) this.getContainer()).getTreeViewer().expandAll(); + + noDefaultAndApplyButton(); + + GridLayout mainLayout = new GridLayout(1, false); + mainLayout.marginWidth = 0; + mainLayout.marginHeight = 0; + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayout(mainLayout); + + infoComposite = + new InfoComposite(composite, emuInstance.getProperties(), emuInstance.getName(), + !emuInstance.isStarted()); + infoComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + AbstractPropertiesComposite.addCompositeChangeListener(compositeChangeListener); + + // there may be some info message for the composite + String initialMessage = infoComposite.getInfoMessage(); + + // if no info message, check if there is some error message + if (initialMessage == null) + { + // if anything is not correct with instance property values, + // show the error message, but as an information to follow + // UI guidelines + initialMessage = infoComposite.getErrorMessage(); + + setValid((initialMessage == null)); + } + + if (initialMessage != null) + { + defaultMessageNeedsReset = true; + setMessage(initialMessage, INFORMATION); + } + + return composite; + } + + /** + * Sets the element that owns the properties + */ + @Override + public void setElement(IAdaptable element) + { + // save the instance for direct use + if (element instanceof AndroidDeviceInstance) + { + emuInstance = (AndroidDeviceInstance) element; + } + + super.setElement(element); + } + + /** + * Performs the OK operation by setting the edited properties as the + * properties for the Android Emulator Device Instance to which this + * Property Page applies (the object for which it was created). + */ + @Override + public boolean performOk() + { + if (emuInstance != null) + { + emuInstance.setProperties(infoComposite.getPropertiesWorkingCopy()); + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, emuInstance)); + } + + return super.performOk(); + } + + /** + * Remove listeners and dispose widgets + */ + @Override + public void dispose() + { + AbstractPropertiesComposite.removeCompositeChangeListener(compositeChangeListener); + infoComposite.dispose(); + super.dispose(); + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AndroidPropertiesStartupOptionsPage.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AndroidPropertiesStartupOptionsPage.java new file mode 100644 index 0000000..5888ab9 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/AndroidPropertiesStartupOptionsPage.java @@ -0,0 +1,228 @@ +/* +* 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.ui; + +import java.util.Properties; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent.InstanceEventType; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbenchPropertyPage; +import org.eclipse.ui.dialogs.PropertyPage; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.SkinFramework; +import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.device.instance.options.StartupOptionsMgt; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeEvent; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeListener; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * DESCRIPTION: + * <br> + * This class implements the Startup Options Property Page for Android Emulator Device Instances. + * <br> + * It shows the Startup Options for the Android Emulator Device Instance on the UI so that the user + * is able to edit it. + * <br> + * RESPONSIBILITY: + * <br> + * - Allow viewing and editing Startup Options of an Android Emulator Device Instance + * <br> + * COLABORATORS: + * <br> + * PropertyPage: extends this class + * <br> + * StartupOptionsComposite: uses this composite for exhibiting startup options on the UI + * <br> + * USAGE: + * <br> + * This class should be defined by the plugin.xml file as a regular Eclipse Property Page. + * It should be enabled for AndroidEmulatorInstance objects. + * + */ +public class AndroidPropertiesStartupOptionsPage extends PropertyPage implements + IWorkbenchPropertyPage, IDevicePropertiesConstants +{ + + // the Android Emulator Device Instance to which this Property Page applies + private AndroidDeviceInstance emuInstance; + + private StartupOptionsComposite startupOptionsComposite; + + // whether this property page will need its default message to be reset + // this happens in case the initial state of the property page when it is + // opened is an erroneous state (any of the properties contain invalid value) + private boolean defaultMessageNeedsReset = false; + + // the default message defined by Eclipse implementation for reset purposes + private final String defaultMessage = getMessage(); + + // handle changes + private final PropertyCompositeChangeListener compositeChangeListener = + new PropertyCompositeChangeListener() + { + public void compositeChanged(PropertyCompositeChangeEvent e) + { + String errorMessage = startupOptionsComposite.getErrorMessage(); + setErrorMessage(errorMessage); + setValid((errorMessage == null) && (getMessage() == null)); + + if (defaultMessageNeedsReset) + { + defaultMessageNeedsReset = false; + setMessage(defaultMessage); + } + } + }; + + /** + * Creates the UI contents of this Property Page. + * It shows the Android Emulator Device Instance properties + * organized into tabs. + */ + @Override + protected Control createContents(Composite parent) + { + // Create Startup Options area + + SkinFramework sm = new SkinFramework(); + IAndroidSkin skin = null; + boolean canCalculateScale = true; + try + { + skin = sm.getSkinById(emuInstance.getSkinId(), emuInstance.getSkinPath()); + } + catch (SkinException e) + { + StudioLogger.error(this.getClass(), + "Error reading instance skin during startup options page creation", e); + canCalculateScale = false; + } + + startupOptionsComposite = + new StartupOptionsComposite(parent, emuInstance.getCommandLineArguments(), skin, + canCalculateScale); + startupOptionsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + AbstractPropertiesComposite.addCompositeChangeListener(compositeChangeListener); + + // If anything is not correct with instance property values, + // show the error message, but as an information to follow + // UI guidelines + String errorMessage = startupOptionsComposite.getErrorMessage(); + setValid((errorMessage == null)); + if (errorMessage != null) + { + defaultMessageNeedsReset = true; + setMessage(errorMessage, INFORMATION); + } + + return startupOptionsComposite; + + } + + /** + * Sets the element that owns the properties + */ + @Override + public void setElement(IAdaptable element) + { + // save the instance for direct use + if (element instanceof AndroidDeviceInstance) + { + emuInstance = (AndroidDeviceInstance) element; + } + + super.setElement(element); + } + + /** + * Performs the OK operation by setting the edited startup options as the + * startup options for the Android Emulator Device Instance to which this + * Property Page applies (the object for which it was created). + * + * @see org.eclipse.jface.preference.PreferencePage#performOk() + */ + @Override + public boolean performOk() + { + save(); + return super.performOk(); + } + + /** + * Performs the Apply operation (which is the same as OK) + * + * @see org.eclipse.jface.preference.PreferencePage#performApply() + */ + @Override + protected void performApply() + { + save(); + super.performApply(); + } + + /** + * Save the edited startup options in the Android Emulator Device Instance + */ + private void save() + { + if (emuInstance != null) + { + Properties emuProperties = emuInstance.getProperties(); + emuProperties.setProperty(IDevicePropertiesConstants.commandline, + StartupOptionsMgt.getParamList()); + emuInstance.setProperties(emuProperties); + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, emuInstance)); + } + } + + /** + * Set the default initial properties + * + * @see org.eclipse.jface.preference.PreferencePage#performDefaults() + */ + @Override + protected void performDefaults() + { + startupOptionsComposite.reloadValues(NativeUIUtils.getDefaultCommandLine()); + super.performDefaults(); + } + + /** + * Remove listeners and dispose widgets + */ + @Override + public void dispose() + { + AbstractPropertiesComposite.removeCompositeChangeListener(compositeChangeListener); + startupOptionsComposite.dispose(); + startupOptionsComposite = null; + super.dispose(); + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/DevicePropertiesPage.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/DevicePropertiesPage.java new file mode 100644 index 0000000..34915b6 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/DevicePropertiesPage.java @@ -0,0 +1,47 @@ +/* +* 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.ui; + +import java.util.Properties; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.ui.IWorkbenchPropertyPage; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.adt.ISerialNumbered; +import com.motorola.studio.android.devices.AbstractDevicePropertyPage; + +public class DevicePropertiesPage extends AbstractDevicePropertyPage implements + IWorkbenchPropertyPage +{ + + private ISerialNumbered androidIntance; + + @Override + public void setElement(IAdaptable element) + { + + this.androidIntance = (ISerialNumbered) element; + + super.setElement(element); + } + + @Override + protected Properties getDeviceProperties() + { + return DDMSFacade.getDeviceProperties(androidIntance.getSerialNumber()); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/DpiScaleCalculatorDialog.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/DpiScaleCalculatorDialog.java new file mode 100644 index 0000000..0e3a015 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/DpiScaleCalculatorDialog.java @@ -0,0 +1,383 @@ +/* +* 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.ui; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.ISkinKeyXmlTags; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +public class DpiScaleCalculatorDialog extends Dialog +{ + private Text screenSizeValue; + + private Button monitorDpiValueButton; + + private Text monitorDpiValue; + + private Text monitorSizeText; + + private Text resultDpivalueText; + + private Integer resultDpiValue; + + private Label resultScaleText; + + private Double resultScaleValue; + + private Text resultScaleValueText; + + private Label errorLabel; + + private final IAndroidSkin skin; + + private final Collection<String> errors = new ArrayList<String>(); + + int size1 = -1; + + int size2 = -1; + + /** + * The Dialog constructor + * + * @param parentShell The shell + * @param skin The selected skin of the AVD being created/edited + */ + protected DpiScaleCalculatorDialog(Shell parentShell, IAndroidSkin skin) + { + super(parentShell); + this.skin = skin; + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell) + */ + @Override + protected void configureShell(Shell newShell) + { + super.configureShell(newShell); + newShell.setText(EmulatorNLS.DPISCALECALCULATOR_Title); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + protected Control createDialogArea(Composite parent) + { + //Main Composite + Composite mainComposite = new Composite(parent, SWT.FILL); + mainComposite.setLayout(new GridLayout(2, false)); + GridData data; + + //Error Area + errorLabel = new Label(mainComposite, SWT.READ_ONLY); + errorLabel.setForeground(new Color(null, 255, 0, 0)); + data = new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1); + errorLabel.setLayoutData(data); + + //Screen Size + Label screenSizeLabel = new Label(mainComposite, SWT.READ_ONLY); + screenSizeLabel.setText(EmulatorNLS.DPISCALECALCULATOR_ScreenSize_Label); + data = new GridData(SWT.LEFT, SWT.CENTER, false, false); + screenSizeLabel.setLayoutData(data); + + screenSizeValue = new Text(mainComposite, SWT.BORDER); + data = new GridData(SWT.FILL, SWT.NULL, true, false); + screenSizeValue.setLayoutData(data); + screenSizeValue.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + validate(); + } + }); + + //Monitor DPI Group + Group monitorDpiGroup = new Group(mainComposite, SWT.SHADOW_OUT); + monitorDpiGroup.setText(EmulatorNLS.DPISCALECALCULATOR_MonitorDpi_Label); + data = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1); + monitorDpiGroup.setLayoutData(data); + monitorDpiGroup.setLayout(new GridLayout(3, false)); + + //Insert Monitor DPI value Option + monitorDpiValueButton = new Button(monitorDpiGroup, SWT.RADIO); + monitorDpiValueButton.setText(EmulatorNLS.DPISCALECALCULATOR_MonitorDpivalue_Label); + monitorDpiValueButton.setSelection(true); + monitorDpiValueButton.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + monitorDpiValue.setEnabled(monitorDpiValueButton.getSelection()); + validate(); + } + }); + data = new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1); + monitorDpiValueButton.setLayoutData(data); + + monitorDpiValue = new Text(monitorDpiGroup, SWT.SINGLE | SWT.BORDER); + data = new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1); + data.widthHint = 100; + monitorDpiValue.setLayoutData(data); + monitorDpiValue.setEnabled(monitorDpiValueButton.getSelection()); + monitorDpiValue.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + validate(); + } + }); + + //Calculate Monitor DPI Option + final Button calculateMonitorDpiButton = new Button(monitorDpiGroup, SWT.RADIO); + calculateMonitorDpiButton.setText(EmulatorNLS.DPISCALECALCULATOR_MonitorDpiSize_Label); + calculateMonitorDpiButton.setSelection(false); + calculateMonitorDpiButton.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + monitorSizeText.setEnabled(calculateMonitorDpiButton.getSelection()); + validate(); + } + }); + + monitorSizeText = new Text(monitorDpiGroup, SWT.SINGLE | SWT.BORDER); + data = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1); + monitorSizeText.setLayoutData(data); + monitorSizeText.setEnabled(calculateMonitorDpiButton.getSelection()); + monitorSizeText.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + validate(); + } + }); + + //Result Group + Group resultGroup = new Group(mainComposite, SWT.SHADOW_OUT); + resultGroup.setText(EmulatorNLS.DPISCALECALCULATOR_ResultGroup_Title); + data = new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1); + resultGroup.setLayoutData(data); + resultGroup.setLayout(new GridLayout(4, false)); + + //Moitor DPI + Label resultDpi = new Label(resultGroup, SWT.READ_ONLY); + resultDpi.setText(EmulatorNLS.DPISCALECALCULATOR_ResultMonitorDpi_Label); + data = new GridData(SWT.LEFT, SWT.CENTER, false, false); + resultDpi.setLayoutData(data); + + resultDpivalueText = new Text(resultGroup, SWT.WRAP | SWT.READ_ONLY); + data = new GridData(SWT.FILL, SWT.NULL, true, false); + resultDpivalueText.setLayoutData(data); + + //Scale + resultScaleText = new Label(resultGroup, SWT.READ_ONLY); + resultScaleText.setText(EmulatorNLS.DPISCALECALCULATOR_ResultScale_Label); + data = new GridData(SWT.LEFT, SWT.CENTER, false, false); + resultScaleText.setLayoutData(data); + + resultScaleValueText = new Text(resultGroup, SWT.WRAP | SWT.READ_ONLY); + data = new GridData(SWT.FILL, SWT.NULL, true, false); + resultScaleValueText.setLayoutData(data); + + mainComposite.layout(); + mainComposite.pack(); + + return null; + } + + /** + * Updates the Monitor DPI and Scale results + * + */ + private void updateResult() + { + + if (monitorDpiValueButton.getSelection()) + { + resultDpiValue = Integer.parseInt(monitorDpiValue.getText()); + } + else + { + resultDpiValue = calculateMonitorDpi(); + } + resultDpivalueText.setText(resultDpiValue.toString()); + + resultScaleValue = calculateScale(); + resultScaleValueText.setText(resultScaleValue.toString()); + } + + /** + * Calculates the Monitor DPI using the user monitor size and the resolution + * + * @return int The calculated Monitor DPI + */ + private int calculateMonitorDpi() + { + float monitorSize = Float.parseFloat(monitorSizeText.getText()); + Dimension b = Toolkit.getDefaultToolkit().getScreenSize(); + float width = b.width; + float height = b.height; + float ratio = width / height; + + double dpi = + Math.round((width / (ratio * (Math.sqrt((Math.pow(monitorSize, 2)) + / (1f + Math.pow(ratio, 2))))))); + + return (int) dpi; + } + + /** + * Calculates the scale to be used using the monitor dpi, the device screen size and the skin main display dimensions + * + * @return + */ + private double calculateScale() + { + double dpi = resultDpiValue; + + if ((skin != null) && (size1 == -1) && (size2 == -1)) + { + try + { + Collection<String> layouts = skin.getAvailableLayouts(); + String defLayout = layouts.toArray()[0].toString(); + + size1 = + skin.getSkinBean(defLayout).getSkinPropertyValue( + ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH); + + size2 = + skin.getSkinBean(defLayout).getSkinPropertyValue( + ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT); + } + catch (SkinException e) + { + StudioLogger.error(DpiScaleCalculatorDialog.class, "Error while calculating scale", //$NON-NLS-1$ + e); + } + + } + if ((size1 > 0) && (size2 > 0)) + { + double diagonalPx = Math.sqrt(Math.pow(size1, 2) + Math.pow(size2, 2)); + double screenSize = Double.parseDouble(screenSizeValue.getText()); + + double scale = (screenSize * dpi) / diagonalPx; + return (Math.round((scale * 100.0))) / 100.0; + } + else + { + getButton(IDialogConstants.OK_ID).setEnabled(false); + } + return 1; + } + + /** + * Validates all the calculator fields + */ + public void validate() + { + final String REGEX_1 = EmulatorNLS.DPISCALECALCULATOR_Regex_TwoDigits; + final String REGEX_2 = "\\d+"; //$NON-NLS-1$ + + final String ERROR_SCREEN_SIZE = EmulatorNLS.DPISCALECALCULATOR_Error_ScreenSize; + final String ERROR_DPI_VALUE = EmulatorNLS.DPISCALECALCULATOR_Error_MonitorDpi; + final String ERROR_MONITOR_SIZE = EmulatorNLS.DPISCALECALCULATOR_Error_MonitorSize; + + errors.clear(); + errorLabel.setText(""); //$NON-NLS-1$ + + if (!screenSizeValue.getText().matches(REGEX_1)) + { + errors.add(ERROR_SCREEN_SIZE); + } + + if (monitorDpiValueButton.getSelection() && !monitorDpiValue.getText().matches(REGEX_2)) + { + errors.add(ERROR_DPI_VALUE); + } + + if (!monitorDpiValueButton.getSelection() && !monitorSizeText.getText().matches(REGEX_1)) + { + errors.add(ERROR_MONITOR_SIZE); + } + + if (errors.size() > 0) + { + getButton(OK).setEnabled(false); + errorLabel.setText((String) errors.toArray()[0]); + errorLabel.pack(); + + resultDpivalueText.setText(""); //$NON-NLS-1$ + resultScaleValueText.setText(""); //$NON-NLS-1$ + } + else + { + getButton(OK).setEnabled(true); + updateResult(); + } + + } + + /** + * Gets the calculated device dpi + * + * @return the device dpi to be used + */ + public String getResultDpivalue() + { + return resultDpiValue.toString(); + } + + /** + * Gets the calculated scale + * + * @return the scale to be used + */ + public String getResultScaleValue() + { + return resultScaleValue.toString(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/InfoComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/InfoComposite.java new file mode 100644 index 0000000..278de88 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/InfoComposite.java @@ -0,0 +1,210 @@ +/* +* 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.ui; + +import java.util.Properties; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; + +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * DESCRIPTION: + * <br> + * This class is a composite which shows all Android Emulator Device Instance information. + * All displayed information can be edited and kept on a Properties object, which can be + * later used to be set to an actual Android Emulator Device Instance object as its new values. + * <br> + * It extends the AbstractPropertiesComposite so as to use its common functionalities. + * <br> + * RESPONSIBILITY: + * <br> + * - Show all available UI information of a Android Emulator Device Instance + * <br> + * COLABORATORS: + * <br> + * AbstractPropertiesComposite: extends this class + * <br> + * IDevicePropertiesConstants: implements this interface in order to have direct use of its + * constants for populating the device instance's Properties object + * <br> + * USAGE: + * <br> + * This composite can be used for any UI that shows all Android Emulator Device Instance + * UI information. It can either allow of stop the editing of the name of the instance, + * while other information is always editable. + * It should be instantiated as a regular composite. + */ +public class InfoComposite extends AbstractPropertiesComposite implements + IDevicePropertiesConstants +{ + + private boolean showEmulatorDefNotFoundMsg = false; + + private Properties emuPropertiesWorkingCopy; + + private String emuName; + + private PropertiesMainComposite mainComposite; + + // the listener to changes on all AbstractPropertiesComposite objects used on this composite + private PropertyCompositeChangeListener listener = new PropertyCompositeChangeListener() + { + public void compositeChanged(PropertyCompositeChangeEvent e) + { + if (e.getSource() instanceof PropertiesMainComposite) + { + emuPropertiesWorkingCopy.setProperty(timeout, mainComposite.getTimeout()); + emuPropertiesWorkingCopy.setProperty(skinId, mainComposite.getSkinId()); + emuPropertiesWorkingCopy.setProperty(useVnc, mainComposite.getUseVnc()); + emuPropertiesWorkingCopy.setProperty(useProxy, mainComposite.getUseProxy()); + emuPropertiesWorkingCopy.setProperty(startFromSnapshot, + mainComposite.getstartFromSnapshot()); + emuPropertiesWorkingCopy.setProperty(saveSnapshot, mainComposite.getSaveSnapshot()); + emuPropertiesWorkingCopy.setProperty(abiType, mainComposite.getAbiType()); + + notifyCompositeChangeListeners(); + } + } + }; + + /** + * Creates a InfoComposite object. + * + * @param parent the parent composite + * @param emuProperties the instance properties to be shown on the UI + * @param emuName the name of the instance + * @param isNameEditable whether the instance name should be made editable or not + * @param areOtherFieldsEditable True if the user will be able to edit other data in the composite; false otherwise + */ + public InfoComposite(Composite parent, Properties emuProperties, String emuName, + boolean areOtherFieldsEditable) + { + super(parent); + + this.emuPropertiesWorkingCopy = (Properties) emuProperties.clone(); + this.emuName = emuName; + + createUI(areOtherFieldsEditable); + + parent.addDisposeListener(new DisposeListener() + { + public void widgetDisposed(DisposeEvent e) + { + AbstractPropertiesComposite.removeCompositeChangeListener(listener); + InfoComposite.this.dispose(); + } + }); + } + + /** + * Creates all the UI for this composite. The listener declared as attribute to this class + * is added to all composites extending AbstractPropertiesComposite. + * Information is organized on 3 tabs:<br> + * - "Main" tab: uses the PropertiesMainComposite;<br> + * - "Advanced" tab: uses the PropertiesAdvancedComposite;<br> + * + * @param editable True if the user will be able to edit data in the composite; false otherwise + */ + private void createUI(boolean editable) + { + GridLayout mainLayout = new GridLayout(1, false); + mainLayout.marginWidth = 0; + mainLayout.marginHeight = 0; + this.setLayout(mainLayout); + + mainComposite = + new PropertiesMainComposite(this, emuName, getProperty(emulatorDefId), + getProperty(timeout), Boolean.parseBoolean(getProperty(useVnc)), + Boolean.parseBoolean(getProperty(useProxy)), + Boolean.parseBoolean(getProperty(useSnapshots)), + Boolean.parseBoolean(getProperty(saveSnapshot)), + Boolean.parseBoolean(getProperty(startFromSnapshot)), + SdkUtils.getTargetByName(getProperty(vmTarget)), getProperty(vmSkin), + getProperty(vmPath), getProperty(abiType), true, false, editable); + mainComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + AbstractPropertiesComposite.addCompositeChangeListener(listener); + } + + /** + * Retrieves the given property from the instance. + * + * @param propertyName the name of the property (key) + * + * @return the property value + */ + private String getProperty(String propertyName) + { + String emuProperty = emuPropertiesWorkingCopy.getProperty(propertyName); + if (emuProperty == null) + { + emuProperty = ""; + } + return emuProperty; + } + + /** + * Retrieves the (potentially) edited properties. + * + * @return the edited properties + */ + public Properties getPropertiesWorkingCopy() + { + return emuPropertiesWorkingCopy; + } + + /** + * Retrieves the error message associated to this composites current state. + * "Main" tab's error message is returned if any; if none, "Skin" tab's error + * message is returned if any; if none, "VM" tab's error message is returned if + * any; if none, <code>null</code> is returned to state there is no error with + * the current state. + * + * @return the error message, or <code>null</code> if there are no errors + */ + @Override + public String getErrorMessage() + { + String errorMessage = mainComposite.getErrorMessage(); + + return errorMessage; + } + + /** + * Retrieves the information message associated to this composites current state. + * + * @return the information message, or <code>null</code> if there are no information messages + */ + public String getInfoMessage() + { + String infoMessage = null; + + if (showEmulatorDefNotFoundMsg) + { + infoMessage = EmulatorNLS.INFO_InfoComposite_EmulatorDefinitionNotFound; + } + + return infoMessage; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/PropertiesMainComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/PropertiesMainComposite.java new file mode 100644 index 0000000..9c3b391 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/PropertiesMainComposite.java @@ -0,0 +1,1287 @@ +/*
+* 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.ui;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.preference.IPreferenceNode;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.preference.PreferenceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.dialogs.WorkbenchPreferenceDialog;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.avd.AvdInfo;
+import com.motorola.studio.android.adt.SdkUtils;
+import com.motorola.studio.android.common.log.StudioLogger;
+import com.motorola.studio.android.emulator.device.IAndroidDeviceConstants;
+import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants;
+import com.motorola.studio.android.emulator.device.definition.AndroidEmuDefMgr;
+import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
+
+/**
+ * DESCRIPTION:
+ * <br>
+ * This class implements the UI for showing all Android Emulator Device Instance main information,
+ * such as its name, description, etc.
+ * <br>
+ * It extends the AbstractPropertiesComposite so as to use its common functionalities.
+ * <br>
+ * RESPONSIBILITY:
+ * <br>
+ * - Show Android Emulator Device Instance main information on the UI
+ * <br>
+ * COLABORATORS:
+ * <br>
+ * AbstractPropertiesComposite: extends this class
+ * <br>
+ * USAGE:
+ * <br>
+ * This class should be added as a regular composite whenever main information on Android Emulator
+ * Device Instance is necessary to be shown and edited on the UI. It can either allow of stop the
+ * editing of the name of the instance, while other information is always editable.
+ */
+//@SuppressWarnings("restriction")
+@SuppressWarnings("restriction")
+public class PropertiesMainComposite extends AbstractPropertiesComposite
+{
+ private static final String ORG_ECLIPSE_UI_NET_NET_PREFERENCES =
+ "org.eclipse.ui.net.NetPreferences"; //$NON-NLS-1$
+
+ private final AndroidEmuDefMgr emuDefMgr = AndroidEmuDefMgr.getInstance();
+
+ private String name = ""; //$NON-NLS-1$
+
+ private String skinId = ""; //$NON-NLS-1$
+
+ private String timeout = ""; //$NON-NLS-1$
+
+ private boolean useVnc;
+
+ private boolean useProxy;
+
+ private boolean useSnapshots;
+
+ // VM Settings
+ private IAndroidTarget vmTarget = null;
+
+ private String vmSkin = ""; //$NON-NLS-1$
+
+ private String sdCardType = ""; //$NON-NLS-1$
+
+ private String sdCardValue = ""; //$NON-NLS-1$
+
+ private String vmPath = ""; //$NON-NLS-1$
+
+ private boolean usingDefaultVmPath;
+
+ private String abiType = "";
+
+ /*
+ * SD Card
+ */
+ private static final String SDCARD_TYPE_NONE = "NONE"; //$NON-NLS-1$
+
+ private static final String SDCARD_TYPE_PATH = "PATH"; //$NON-NLS-1$
+
+ private static final String SDCARD_TYPE_SIZE = "SIZE"; //$NON-NLS-1$
+
+ private static final String SDCARD_PATH_EXTENSION = ".img"; //$NON-NLS-1$
+
+ private static final String[] SDCARD_SIZE_UNITS = new String[]
+ {
+ "KB", "MB" //$NON-NLS-1$ //$NON-NLS-2$
+ };
+
+ private Button saveSnapshotButton;
+
+ private Button startFromSnapshotButton;
+
+ private boolean saveSnapshots;
+
+ private boolean startFromSnapshots;
+
+ private Combo abiTypeCombo;
+
+ /**
+ * Creates a PropertiesMainComposite object.
+ *
+ * @param parent the parent composite
+ * @param name the instance name
+ * @param description the instance description
+ * @param emulatorDefId the instance emulator definition id
+ * @param isNameEditable whether the name should be editable or not
+ * @param areOtherFieldsEditable True if the user will be able to edit other data in the composite; false otherwise
+ */
+ public PropertiesMainComposite(Composite parent, String name, String emulatorDefId,
+ String timeout, boolean useVnc, boolean useProxy, boolean useSnapshot,
+ boolean saveSnapshot, boolean startFromSnapshot, IAndroidTarget vmTarget,
+ String vmSkin, String vmPath, String abiType, boolean showName,
+ boolean areVmSettingsEditable, boolean areOtherFieldsEditable)
+
+ {
+ super(parent);
+
+ this.name = name;
+ this.timeout = timeout;
+ this.useVnc = useVnc;
+ this.useProxy = useProxy;
+ this.abiType = abiType;
+ skinId = emuDefMgr.getSkinId(emulatorDefId);
+
+ this.vmTarget = vmTarget;
+ this.vmSkin = vmSkin;
+ this.vmPath = vmPath;
+ this.useSnapshots = useSnapshot;
+ this.saveSnapshots = saveSnapshot;
+ this.startFromSnapshots = startFromSnapshot;
+
+ createUI(showName, areVmSettingsEditable, areOtherFieldsEditable);
+
+ //Set context Help (not available yet)
+ PlatformUI.getWorkbench().getHelpSystem()
+ .setHelp(parent, IAndroidDeviceConstants.MAIN_PAGE_HELP);
+ }
+
+ /**
+ * Creates a PropertiesMainComposite object with instance name editing not allowed.
+ *
+ * @param parent the parent composite
+ * @param name the instance name
+ * @param description the instance description
+ * @param emulatorDefId the instance emulator definition name
+ */
+ public PropertiesMainComposite(Composite parent, String emulatorDefId, String timeout,
+ boolean useVnc, boolean useProxy, boolean useSnapshot, boolean saveSnapshot,
+ boolean startFromSnapshot, IAndroidTarget vmTarget, String vmSkin, String vmPath,
+ String abiType)
+ {
+ this(parent, "", emulatorDefId, timeout, useVnc, useProxy, useSnapshot, saveSnapshot,
+ startFromSnapshot, vmTarget, vmSkin, vmPath, abiType, false, true, true);
+
+ //Set context Help (not available yet)
+ PlatformUI.getWorkbench().getHelpSystem()
+ .setHelp(parent, IAndroidDeviceConstants.MAIN_PAGE_HELP);
+ }
+
+ /**
+ * @param showName
+ * @param areVmSettingsEditable
+ * @param areOtherFieldsEditable
+ */
+ private void createUI(boolean showName, boolean areVmSettingsEditable,
+ boolean areOtherFieldsEditable)
+ {
+ GridData data;
+ Composite mainComposite = this;
+ Collection<Control> otherFields = new HashSet<Control>();
+
+ setMainLayout(2);
+
+ if (showName)
+ {
+ Label nameLabel = new Label(mainComposite, SWT.READ_ONLY);
+ nameLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_NameLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ nameLabel.setLayoutData(data);
+
+ Text nameTextLabel = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ nameTextLabel.setText(name);
+ data = new GridData(SWT.FILL, SWT.NULL, true, false);
+ nameTextLabel.setLayoutData(data);
+ }
+
+ /*
+ * (Not) Editable area
+ */
+ if (areVmSettingsEditable)
+ {
+ createEditableVmUI();
+ }
+ else
+ {
+ createNotEditableVmUI();
+ }
+
+ /*
+ * Emulator proxy settings
+ */
+ Group proxyGroup = new Group(mainComposite, SWT.SHADOW_OUT);
+ proxyGroup.setText(EmulatorNLS.PropertiesMainComposite_ProxySettings_GroupTitle);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1);
+ proxyGroup.setLayoutData(data);
+ proxyGroup.setLayout(new GridLayout(3, false));
+
+ final Button proxyChkbox = new Button(proxyGroup, SWT.CHECK);
+ proxyChkbox.setText(EmulatorNLS.PropertiesMainComposite_ProxySettings_CheckboxLabel);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ proxyChkbox.setSelection(useProxy);
+ proxyChkbox.setLayoutData(data);
+ proxyChkbox.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ useProxy = proxyChkbox.getSelection();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ Link networkSettings = new Link(proxyGroup, SWT.NONE);
+ networkSettings.setText(EmulatorNLS.PropertiesMainComposite_ProxySettings_LinkToPreference);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1);
+ networkSettings.setLayoutData(data);
+
+ networkSettings.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ openNetworkPreferences();
+ }
+ });
+
+ //Snapshot group
+
+ Group snapshotGroup = new Group(mainComposite, SWT.SHADOW_OUT);
+ snapshotGroup.setText(EmulatorNLS.PropertiesMainComposite_SnapshotSettings);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1);
+ snapshotGroup.setLayoutData(data);
+ snapshotGroup.setLayout(new GridLayout(3, false));
+
+ final Button enableSnapshotButton = new Button(snapshotGroup, SWT.CHECK);
+ enableSnapshotButton.setText(EmulatorNLS.PropertiesMainComposite_UseSnapshot);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ enableSnapshotButton.setLayoutData(data);
+ enableSnapshotButton.setEnabled(areVmSettingsEditable);
+ enableSnapshotButton.setSelection(useSnapshots);
+ enableSnapshotButton.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ useSnapshots = enableSnapshotButton.getSelection();
+ notifyCompositeChangeListeners();
+ startFromSnapshotButton.setEnabled(useSnapshots);
+ saveSnapshotButton.setEnabled(useSnapshots);
+ }
+ });
+
+ startFromSnapshotButton = new Button(snapshotGroup, SWT.CHECK);
+ startFromSnapshotButton.setText(EmulatorNLS.PropertiesMainComposite_startFromSnapshot);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ startFromSnapshotButton.setLayoutData(data);
+ startFromSnapshotButton.setEnabled(useSnapshots);
+ startFromSnapshotButton.setSelection(startFromSnapshots);
+
+ startFromSnapshotButton.addSelectionListener(new SelectionAdapter()
+ {
+
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ startFromSnapshots = startFromSnapshotButton.getSelection();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ saveSnapshotButton = new Button(snapshotGroup, SWT.CHECK);
+ saveSnapshotButton.setText(EmulatorNLS.PropertiesMainComposite_SaveSnapshot);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ saveSnapshotButton.setLayoutData(data);
+ saveSnapshotButton.setEnabled(useSnapshots);
+ saveSnapshotButton.setSelection(saveSnapshots);
+
+ saveSnapshotButton.addSelectionListener(new SelectionAdapter()
+ {
+
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ saveSnapshots = saveSnapshotButton.getSelection();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ otherFields.add(proxyGroup);
+ otherFields.add(proxyChkbox);
+ otherFields.add(networkSettings);
+ otherFields.add(snapshotGroup);
+
+ /*
+ * Emulator Window mode area
+ */
+
+ //Only for windows and linux platforms
+ if (!Platform.getOS().equals(Platform.OS_MACOSX))
+ {
+ Group windowGroup = new Group(mainComposite, SWT.SHADOW_OUT);
+ windowGroup
+ .setText(EmulatorNLS.UI_PropertiesMainComposite_EmulatorWindowMode_GroupTitle);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1);
+ windowGroup.setLayoutData(data);
+ windowGroup.setLayout(new GridLayout(3, false));
+
+ final Button nativeRadio = new Button(windowGroup, SWT.RADIO);
+ nativeRadio
+ .setText(EmulatorNLS.UI_PropertiesMainComposite_EmulatorWindowMode_NativeLabel);
+ nativeRadio.setSelection(!useVnc);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ nativeRadio.setLayoutData(data);
+ nativeRadio.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ useVnc = !nativeRadio.getSelection();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ final Button vncRadio = new Button(windowGroup, SWT.RADIO);
+ vncRadio.setText(EmulatorNLS.UI_PropertiesMainComposite_EmulatorWindowMode_VncLabel);
+ vncRadio.setSelection(useVnc);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ vncRadio.setLayoutData(data);
+
+ otherFields.add(windowGroup);
+ otherFields.add(nativeRadio);
+ otherFields.add(vncRadio);
+ }
+
+ /*
+ * Timeout
+ */
+ Label timeoutLabel = new Label(mainComposite, SWT.READ_ONLY);
+ timeoutLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_TimeoutLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ timeoutLabel.setLayoutData(data);
+
+ final Text timeoutText = new Text(mainComposite, SWT.SINGLE | SWT.BORDER);
+ otherFields.add(timeoutText);
+ timeoutText.setText(timeout);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ timeoutText.setLayoutData(data);
+
+ timeoutText.addModifyListener(new ModifyListener()
+ {
+ public void modifyText(ModifyEvent e)
+ {
+ timeout = timeoutText.getText().trim();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ addInstanceListener(otherFields);
+ updateWidgetEnableStatus(areOtherFieldsEditable, otherFields);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void openNetworkPreferences()
+ {
+ // Makes the network preferences dialog manager
+ PreferenceManager manager = PlatformUI.getWorkbench().getPreferenceManager();
+ IPreferenceNode networkNode = null;
+ for (IPreferenceNode node : (List<IPreferenceNode>) manager
+ .getElements(PreferenceManager.PRE_ORDER))
+ {
+ if (node.getId().equals(ORG_ECLIPSE_UI_NET_NET_PREFERENCES))
+ {
+ networkNode = node;
+ break;
+ }
+ }
+ PreferenceManager prefMan = new PreferenceManager();
+ if (networkNode != null)
+ {
+ prefMan.addToRoot(networkNode);
+ }
+ PreferenceDialog networkPreferencesDialog =
+ new WorkbenchPreferenceDialog(getShell(), prefMan);
+ networkPreferencesDialog.create();
+ networkPreferencesDialog.open();
+ }
+
+ /**
+ * Creates the not editable UI for the VM settings.
+ */
+ private void createNotEditableVmUI()
+ {
+ GridData data;
+ Composite mainComposite = this;
+
+ Label tagetLabel = new Label(mainComposite, SWT.READ_ONLY);
+ tagetLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_TargetLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ tagetLabel.setLayoutData(data);
+
+ final Text targetText = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ targetText.setText(vmTarget.getName());
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ targetText.setLayoutData(data);
+
+ Label abiTypeLabel = new Label(mainComposite, SWT.READ_ONLY);
+ abiTypeLabel.setText(EmulatorNLS.PropertiesMainComposite_ABITypeLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ abiTypeLabel.setLayoutData(data);
+
+ final Text abiTypeText = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ abiTypeText.setText(AvdInfo.getPrettyAbiType(abiType));
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ abiTypeText.setLayoutData(data);
+
+ Label skinLabel = new Label(mainComposite, SWT.READ_ONLY);
+ skinLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_SkinLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ skinLabel.setLayoutData(data);
+
+ final Text vmSkinText = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ vmSkinText.setText(vmSkin);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ vmSkinText.setLayoutData(data);
+
+ Label pathLabel = new Label(mainComposite, SWT.READ_ONLY);
+ pathLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_PathLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ pathLabel.setLayoutData(data);
+
+ final Text pathText = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ pathText.setText(vmPath);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ data.widthHint = 100;
+ pathText.setLayoutData(data);
+
+ /*
+ * SD Card info
+ */
+ AvdInfo vmInfo = SdkUtils.getValidVm(name);
+ Properties configFile = new Properties();
+ BufferedReader input = null;
+ try
+ {
+
+ // it's necessary to read the file instead of load the Properties file because
+ // there are "\" in the path, which are removed by the Properties class load method, since
+ // they mean line break in a properties file
+ input = new BufferedReader(new FileReader(vmInfo.getConfigFile()));
+
+ String line = null;
+ String[] property = null;
+ while ((line = input.readLine()) != null)
+ {
+ property = line.split("="); //$NON-NLS-1$
+ if ((property[0] != null) && (property[1] != null))
+ {
+ if ((!property[0].equals("")) && (!property[1].equals(""))) //$NON-NLS-1$ //$NON-NLS-2$
+ {
+ configFile.put(property[0], property[1]);
+ }
+ }
+ }
+
+ if (configFile.getProperty(IDevicePropertiesConstants.configSDCardPath) != null)
+ {
+
+ Label sdCardPathLabel = new Label(mainComposite, SWT.READ_ONLY);
+ sdCardPathLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_SDCardPathLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ sdCardPathLabel.setLayoutData(data);
+
+ final Text sdCardPathText = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ sdCardPathText.setText(configFile
+ .getProperty(IDevicePropertiesConstants.configSDCardPath));
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ data.widthHint = 100;
+ sdCardPathText.setLayoutData(data);
+
+ }
+
+ if (configFile.getProperty(IDevicePropertiesConstants.configSDCardSize) != null)
+ {
+ Label sdCardPathLabel = new Label(mainComposite, SWT.READ_ONLY);
+ sdCardPathLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_SDCardSizeLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ sdCardPathLabel.setLayoutData(data);
+
+ final Text sdCardPathText = new Text(mainComposite, SWT.WRAP | SWT.READ_ONLY);
+ sdCardPathText.setText(configFile
+ .getProperty(IDevicePropertiesConstants.configSDCardSize));
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ sdCardPathText.setLayoutData(data);
+ }
+
+ }
+ catch (FileNotFoundException e)
+ {
+ StudioLogger.error("Could not find config file for AVD: " + name); //$NON-NLS-1$
+ }
+ catch (IOException e)
+ {
+ StudioLogger.error("Could not read config file for AVD: " + name); //$NON-NLS-1$
+ }
+ finally
+ {
+ try
+ {
+ if (input != null)
+ {
+ input.close();
+ }
+ }
+ catch (IOException e)
+ {
+ StudioLogger.error("Could not close input stream: ", e.getMessage()); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Creates the editable UI for the VM settings.
+ */
+ private void createEditableVmUI()
+ {
+ GridData data;
+ Composite mainComposite = this;
+
+ Label tagetLabel = new Label(mainComposite, SWT.READ_ONLY);
+ tagetLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_TargetLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ tagetLabel.setLayoutData(data);
+
+ final Combo targetCombo = new Combo(mainComposite, SWT.READ_ONLY);
+ populateTargetCombo(targetCombo);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1);
+ targetCombo.setLayoutData(data);
+
+ Label skinLabel = new Label(mainComposite, SWT.READ_ONLY);
+ skinLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_SkinLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ skinLabel.setLayoutData(data);
+
+ final Combo vmSkinCombo = new Combo(mainComposite, SWT.READ_ONLY);
+ populateSkinCombo(vmSkinCombo);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1);
+ vmSkinCombo.setLayoutData(data);
+
+ vmSkinCombo.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ if ((vmSkinCombo != null) && !"".equals(vmSkinCombo.getText())) //$NON-NLS-1$
+ {
+ vmSkin = (String) vmSkinCombo.getData(vmSkinCombo.getText());
+
+ }
+ else
+ {
+ vmSkin = ""; //$NON-NLS-1$
+ }
+ notifyCompositeChangeListeners();
+
+ }
+ });
+
+ Label abiTypeLabel = new Label(mainComposite, SWT.READ_ONLY);
+ abiTypeLabel.setText(EmulatorNLS.PropertiesMainComposite_ABITypeLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ abiTypeLabel.setLayoutData(data);
+
+ abiTypeCombo = new Combo(mainComposite, SWT.READ_ONLY);
+ populateAbiTypeCombo(abiTypeCombo);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1);
+ abiTypeCombo.setLayoutData(data);
+
+ abiTypeCombo.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ if ((vmSkinCombo != null) && !"".equals(vmSkinCombo.getText())) //$NON-NLS-1$
+ {
+ abiType = (String) abiTypeCombo.getData(abiTypeCombo.getText());
+
+ }
+ else
+ {
+ abiType = SdkConstants.ABI_ARMEABI;
+ }
+ notifyCompositeChangeListeners();
+
+ }
+ });
+
+ targetCombo.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ IAndroidTarget newTarget = null;
+ if ((targetCombo != null) && !"".equals(targetCombo.getText())) //$NON-NLS-1$
+ {
+ newTarget = (IAndroidTarget) targetCombo.getData(targetCombo.getText());
+ }
+
+ if (newTarget != vmTarget)
+ {
+ vmTarget = newTarget;
+ populateSkinCombo(vmSkinCombo);
+ populateAbiTypeCombo(abiTypeCombo);
+ notifyCompositeChangeListeners();
+ }
+ }
+
+ });
+
+ Group vmPathGroup = new Group(mainComposite, SWT.SHADOW_OUT);
+ vmPathGroup.setText(EmulatorNLS.UI_PropertiesMainComposite_PathGroupTitle);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1);
+ vmPathGroup.setLayoutData(data);
+ vmPathGroup.setLayout(new GridLayout(3, false));
+
+ usingDefaultVmPath = vmPath.equals(IDevicePropertiesConstants.defaultVmPath);
+
+ final Button vmPathCheckbox = new Button(vmPathGroup, SWT.CHECK);
+ vmPathCheckbox.setText(EmulatorNLS.UI_PropertiesMainComposite_UseDefaultPath);
+ vmPathCheckbox.setSelection(usingDefaultVmPath);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ vmPathCheckbox.setLayoutData(data);
+
+ Label pathLabel = new Label(vmPathGroup, SWT.READ_ONLY);
+ pathLabel.setText(EmulatorNLS.UI_PropertiesMainComposite_PathLabel);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ pathLabel.setLayoutData(data);
+
+ final Text pathText = new Text(vmPathGroup, SWT.SINGLE | SWT.BORDER);
+ pathText.setText(vmPath);
+ pathText.setEnabled(!usingDefaultVmPath);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ data.widthHint = 100;
+ pathText.setLayoutData(data);
+
+ final Button pathBrowseButton = new Button(vmPathGroup, SWT.PUSH);
+ pathBrowseButton.setText(EmulatorNLS.UI_General_BrowseButtonLabel);
+ pathBrowseButton.setEnabled(!usingDefaultVmPath);
+ data = new GridData(SWT.FILL, SWT.FILL, false, false);
+ pathBrowseButton.setLayoutData(data);
+
+ vmPathCheckbox.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ usingDefaultVmPath = vmPathCheckbox.getSelection();
+ pathText.setEnabled(!usingDefaultVmPath);
+ pathBrowseButton.setEnabled(!usingDefaultVmPath);
+ if (usingDefaultVmPath)
+ {
+ vmPath = IDevicePropertiesConstants.defaultVmPath;
+ }
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ pathText.addModifyListener(new ModifyListener()
+ {
+ public void modifyText(ModifyEvent e)
+ {
+ vmPath = pathText.getText().trim();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ pathBrowseButton.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ DirectoryDialog dirDialog = new DirectoryDialog(getShell(), SWT.OPEN);
+
+ if (!vmPath.trim().equals("")) //$NON-NLS-1$
+ {
+ dirDialog.setFilterPath(vmPath);
+ }
+ else
+ {
+ dirDialog.setFilterPath("/"); //$NON-NLS-1$
+ }
+
+ dirDialog.setText(EmulatorNLS.UI_PropertiesMainComposite_PathGroupTitle);
+
+ String vmPath = dirDialog.open();
+
+ if (vmPath != null)
+ {
+ pathText.setText(vmPath);
+ notifyCompositeChangeListeners();
+ }
+ }
+ });
+
+ /*
+ * SD Card Area
+ */
+
+ sdCardType = SDCARD_TYPE_NONE;
+
+ Group sdCardGroup = new Group(mainComposite, SWT.SHADOW_OUT);
+ sdCardGroup.setText(EmulatorNLS.UI_PropertiesMainComposite_SDCardLabel);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1);
+ sdCardGroup.setLayoutData(data);
+ sdCardGroup.setLayout(new GridLayout(3, false));
+
+ // none
+ final Button noneSDCardCheckbox = new Button(sdCardGroup, SWT.RADIO);
+ noneSDCardCheckbox.setText(EmulatorNLS.UI_PropertiesMainComposite_SDCardNoneLabel);
+ noneSDCardCheckbox.setSelection(true);
+ data = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
+ noneSDCardCheckbox.setLayoutData(data);
+ noneSDCardCheckbox.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ sdCardType = SDCARD_TYPE_NONE;
+ sdCardValue = ""; //$NON-NLS-1$
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ // existing
+ final Button existingSDCardCheckbox = new Button(sdCardGroup, SWT.RADIO);
+ existingSDCardCheckbox.setText(EmulatorNLS.UI_PropertiesMainComposite_SDCardExistingLabel);
+ final Text existingSDCardPath = new Text(sdCardGroup, SWT.SINGLE | SWT.BORDER);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ data.widthHint = 100;
+ existingSDCardPath.setLayoutData(data);
+ existingSDCardPath.setEnabled(false);
+ final Button existingSDCardButton = new Button(sdCardGroup, SWT.PUSH);
+ existingSDCardButton.setText(EmulatorNLS.UI_General_BrowseButtonLabel);
+ data = new GridData(SWT.FILL, SWT.FILL, false, false);
+ existingSDCardButton.setLayoutData(data);
+ existingSDCardButton.setEnabled(false);
+
+ existingSDCardCheckbox.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ sdCardType = SDCARD_TYPE_PATH;
+ existingSDCardPath.setEnabled(existingSDCardCheckbox.getSelection());
+ existingSDCardButton.setEnabled(existingSDCardCheckbox.getSelection());
+ sdCardValue = existingSDCardPath.getText();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ existingSDCardPath.addModifyListener(new ModifyListener()
+ {
+ public void modifyText(ModifyEvent e)
+ {
+ sdCardValue = existingSDCardPath.getText();
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ existingSDCardButton.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ String selectedPath = null;
+
+ FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN);
+ String[] filterExtensions =
+ {
+ "*" + SDCARD_PATH_EXTENSION //$NON-NLS-1$
+ };
+
+ fileDialog.setFilterExtensions(filterExtensions);
+ selectedPath = fileDialog.open();
+
+ if (selectedPath != null)
+ {
+ existingSDCardPath.setText(selectedPath);
+ }
+ }
+ });
+
+ // new
+ final Button newSDCardCheckbox = new Button(sdCardGroup, SWT.RADIO);
+ newSDCardCheckbox.setText(EmulatorNLS.UI_PropertiesMainComposite_SDCardNewLabel);
+ final Text newSDCardSize = new Text(sdCardGroup, SWT.SINGLE | SWT.BORDER);
+ data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ data.widthHint = 100;
+ newSDCardSize.setLayoutData(data);
+ newSDCardSize.setEnabled(false);
+ final Combo newSDCardUnit = new Combo(sdCardGroup, SWT.READ_ONLY);
+ for (String unit : SDCARD_SIZE_UNITS)
+ {
+ newSDCardUnit.add(unit);
+ }
+ newSDCardUnit.select(0);
+ newSDCardUnit.setEnabled(false);
+ data = new GridData(SWT.FILL, SWT.FILL, false, false);
+ newSDCardUnit.setLayoutData(data);
+
+ newSDCardCheckbox.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ sdCardType = SDCARD_TYPE_SIZE;
+ sdCardValue = newSDCardSize.getText();
+ newSDCardSize.setEnabled(newSDCardCheckbox.getSelection());
+ newSDCardUnit.setEnabled(newSDCardCheckbox.getSelection());
+ sdCardValue = newSDCardSize.getText() + newSDCardUnit.getText().charAt(0);
+ notifyCompositeChangeListeners();
+ }
+ });
+
+ ModifyListener newSDCardListener = new ModifyListener()
+ {
+ public void modifyText(ModifyEvent e)
+ {
+ sdCardValue = newSDCardSize.getText() + newSDCardUnit.getText().charAt(0);
+ notifyCompositeChangeListeners();
+ }
+ };
+
+ newSDCardSize.addModifyListener(newSDCardListener);
+ newSDCardUnit.addModifyListener(newSDCardListener);
+ }
+
+ private void populateAbiTypeCombo(Combo abiTypeCombo)
+ {
+ // System Images represents the ABI types
+ ISystemImage[] images = vmTarget.getSystemImages();
+
+ // in case no images are found, get try its parent
+ if ((images == null) || ((images.length == 0) && !vmTarget.isPlatform()))
+ {
+ images = vmTarget.getParent().getSystemImages();
+ }
+
+ // always clean abi combo since it will be reloaded
+ abiTypeCombo.removeAll();
+
+ int i = 0;
+ if ((images != null) && (images.length > 0))
+ {
+ for (ISystemImage image : images)
+ {
+ String prettyAbiName = AvdInfo.getPrettyAbiType(image.getAbiType());
+ abiTypeCombo.add(prettyAbiName);
+ abiTypeCombo.setData(prettyAbiName, image.getAbiType());
+ if (image.getAbiType().equals(abiType))
+ {
+ abiTypeCombo.select(i);
+ }
+ i++;
+ }
+
+ if (abiTypeCombo.getSelectionIndex() == -1)
+ {
+ abiTypeCombo.select(0);
+ abiType = (String) abiTypeCombo.getData(abiTypeCombo.getItem(0));
+ }
+ }
+
+ }
+
+ /**
+ * Populate VM Target combo box.
+ *
+ * @param targetCombo
+ */
+ private void populateTargetCombo(Combo targetCombo)
+ {
+ IAndroidTarget[] targets = SdkUtils.getAllTargets();
+ if ((targets != null) && (targets.length > 0))
+ {
+ for (int i = 0; i < targets.length; i++)
+ {
+ String label =
+ targets[i].isPlatform() ? targets[i].getName() : targets[i].getName()
+ + " (" + targets[i].getParent().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ targetCombo.add(label);
+ targetCombo.setData(label, targets[i]);
+
+ if (targets[i].getName().equals(vmTarget.getName()))
+ {
+ targetCombo.select(i);
+ }
+ }
+ }
+ //Set context Help (not available yet)
+ PlatformUI.getWorkbench().getHelpSystem()
+ .setHelp(this, IAndroidDeviceConstants.MAIN_PAGE_HELP);
+ }
+
+ /**
+ * Populate VM Skin combo box.
+ *
+ * @param skinCombo
+ */
+ private void populateSkinCombo(Combo skinCombo)
+ {
+ skinCombo.removeAll();
+ skinCombo.clearSelection();
+
+ if (vmTarget != null)
+ {
+ String[] skins = vmTarget.getSkins();
+ String defaultSkin = vmTarget.getDefaultSkin();
+
+ for (int i = 0; i < skins.length; i++)
+ {
+ skinCombo.add(skins[i]);
+ skinCombo.setData(skins[i], skins[i]);
+
+ if (skins[i].equals(defaultSkin))
+ {
+ skinCombo.select(i);
+ vmSkin = defaultSkin;
+ }
+ }
+
+ // if there is no selection, select the first
+ if (skinCombo.getSelectionIndex() < 0)
+ {
+ if (skinCombo.getItemCount() > 0)
+ {
+ skinCombo.select(0);
+ vmSkin = skinCombo.getItem(0);
+ }
+ }
+
+ skinCombo.setEnabled(true);
+ }
+ else
+ {
+ skinCombo.setEnabled(false);
+ }
+
+ //Set context Help (not available yet)
+ PlatformUI.getWorkbench().getHelpSystem()
+ .setHelp(this, IAndroidDeviceConstants.MAIN_PAGE_HELP);
+ }
+
+ public String getSkinId()
+ {
+ return skinId;
+ }
+
+ /**
+ * Retrieves the timeout.
+ *
+ * @return the timeout
+ */
+ public String getTimeout()
+ {
+ return timeout;
+ }
+
+ public String getUseVnc()
+ {
+ return (useVnc ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ public String getUseProxy()
+ {
+ return (useProxy ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Retrieves the error message associated to this composites current state.
+ * The order of precedence of error is the same as the fields displayed on the
+ * UI, which means errors on fields drawn first are shown with a higher precedence
+ * than the errors of fields drawn last.
+ * The instance description field is the only non required field.
+ *
+ * @return the error message, or <code>null</code> if there are no errors
+ */
+ @Override
+ public String getErrorMessage()
+ {
+ String errorMessage = null;
+
+ // VM Settings Check
+ if ("".equals(vmTarget)) //$NON-NLS-1$
+ {
+ //Check if Target isn't empty
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_VmTargetEmpty;
+ }
+ else if ("".equals(vmSkin)) //$NON-NLS-1$
+ {
+ //Check if Skin isn't empty
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_VmSkinEmpty;
+ }
+ else
+ {
+ if (!usingDefaultVmPath)
+ {
+ //Check if Path is valid ("" is valid too)
+ File vmPathLocation = new File(vmPath);
+ if ((!vmPathLocation.exists()) || (!vmPathLocation.isDirectory()))
+ {
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_VmPathInvalid;
+ }
+
+ }
+ }
+
+ //ABI Type
+ if (errorMessage == null)
+ {
+ if ((abiTypeCombo != null)
+ && ((abiTypeCombo.getItemCount() == 0) || (abiTypeCombo.getSelectionIndex() == -1))) //$NON-NLS-1$
+ {
+ //no item available or not ABI selected
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_ABINotAvailable;
+ }
+ }
+
+ // SD Card
+ if (errorMessage == null)
+ {
+ if (sdCardType.equalsIgnoreCase(SDCARD_TYPE_PATH))
+ {
+ if (sdCardValue == null) //$NON-NLS-1$
+ {
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_MissingSDCardPath;
+ }
+ else if (!isValidSDCard(sdCardValue))
+ {
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_SDCardPathIsNotValid;
+ }
+ }
+ else if (sdCardType.equalsIgnoreCase(SDCARD_TYPE_SIZE))
+ {
+
+ int sdcardSize = -1;
+ String unit =
+ sdCardValue.length() > 0 ? sdCardValue.substring(sdCardValue.length() - 1)
+ .toLowerCase() : "k"; //$NON-NLS-1$
+
+ if ((sdCardValue == null) || (sdCardValue.equals(""))) //$NON-NLS-1$
+ {
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_MissingSDCardSize;
+ }
+ else
+ {
+
+ try
+ {
+ sdcardSize =
+ Integer.parseInt(sdCardValue.substring(0, sdCardValue.length() - 1));
+ }
+ catch (NumberFormatException e)
+ {
+ //do nothing
+ }
+
+ if ((unit.equals("m") && (sdcardSize < 9)) //$NON-NLS-1$
+ || (unit.equals("k") && (sdcardSize < (9 * 1024)))) //$NON-NLS-1$
+ {
+ errorMessage =
+ EmulatorNLS.ERR_PropertiesMainComposite_SDCardSizeIsNotPositiveInteger;
+ }
+ }
+ }
+ }
+
+ //Timeout
+ if (errorMessage == null)
+ {
+ if (timeout.equals("")) //$NON-NLS-1$
+ {
+ errorMessage = EmulatorNLS.ERR_PropertiesMainComposite_MissingTimeoutValue;
+ }
+ else
+ {
+ if (!isPositiveInteger(timeout))
+ {
+ errorMessage =
+ EmulatorNLS.ERR_PropertiesMainComposite_TimeoutValueIsNotPositiveInteger;
+ }
+ }
+ }
+
+ return errorMessage;
+ }
+
+ /**
+ * Check if a string is a valid SD Card Path
+ *
+ * @param text the string to be analyzed
+ * @return true if the string is a valid SD Card path, false otherwise
+ */
+ private boolean isValidSDCard(String text)
+ {
+ boolean result = true;
+
+ File file = new File(text);
+
+ if ((!file.exists()) || (file.isDirectory())
+ || (!SDCARD_PATH_EXTENSION.equals("." + (new Path(text)).getFileExtension()))) //$NON-NLS-1$
+ {
+ result = false;
+ }
+
+ return result;
+ }
+
+ /**
+ * Check if a string is a positive integer
+ *
+ * @param text the string to be analyzed
+ * @return true if the string is a positive integer, false otherwise
+ */
+ private boolean isPositiveInteger(String text)
+ {
+ int intValue = 0;
+ boolean isPositive = true;
+
+ try
+ {
+ intValue = Integer.parseInt(text);
+ }
+ catch (NumberFormatException e)
+ {
+ isPositive = false;
+ }
+
+ if (intValue <= 0)
+ {
+ isPositive = false;
+ }
+
+ return isPositive;
+ }
+
+ /**
+ * Retrieves the VM Target.
+ *
+ * @return the vmTarget
+ */
+ public IAndroidTarget getVmTarget()
+ {
+ return vmTarget;
+ }
+
+ /**
+ * Retrieves the Abi Type.
+ *
+ * @return the VM Abi Type
+ */
+ public String getAbiType()
+ {
+ return abiType != null ? abiType : SdkConstants.ABI_ARMEABI;
+ }
+
+ /**
+ * Retrieves the VM Skin.
+ *
+ * @return the vmSkin
+ */
+ public String getVmSkin()
+ {
+ return vmSkin;
+ }
+
+ /**
+ * Retrieves the VM Path.
+ *
+ * @return the vmPath
+ */
+ public String getVmPath()
+ {
+ return vmPath + File.separator + name + IDevicePropertiesConstants.defaultVmFolderSuffix;
+ }
+
+ /**
+ * Retrieves the SD Card info
+ *
+ * @return the sdCard
+ */
+ public String getSDCard()
+ {
+ return sdCardValue;
+ }
+
+ /**
+ * Set the name of the AVD
+ * @param name: a not null String with the name of the AVD
+ */
+ public void setName(String name)
+ {
+ this.name = name == null ? "" : name; //$NON-NLS-1$
+ }
+
+ /**
+ * @return
+ */
+ public String getUseSnapshot()
+ {
+ return (useSnapshots ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
+ }
+
+ /**
+ * @return
+ */
+ public String getstartFromSnapshot()
+ {
+ return (startFromSnapshots ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
+ }
+
+ /**
+ * @return
+ */
+ public String getSaveSnapshot()
+ {
+ return (saveSnapshots ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
+ }
+}
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/StartupOptionsComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/StartupOptionsComposite.java new file mode 100644 index 0000000..b6f3f9c --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/StartupOptionsComposite.java @@ -0,0 +1,435 @@ +/* +* 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.ui; + +import java.util.List; + +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.fieldassist.ControlDecoration; +import org.eclipse.jface.fieldassist.FieldDecoration; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.DirectoryDialog; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.device.IAndroidDeviceConstants; +import com.motorola.studio.android.emulator.device.instance.options.IStartupOptionsConstants; +import com.motorola.studio.android.emulator.device.instance.options.StartupOption; +import com.motorola.studio.android.emulator.device.instance.options.StartupOptionsGroup; +import com.motorola.studio.android.emulator.device.instance.options.StartupOptionsMgt; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * DESCRIPTION: + * <br> + * This class implements the UI for showing all Android Emulator Device Instance startup options information. + * <br> + * It extends the AbstractPropertiesComposite so as to use its common functionalities. + * <br> + * RESPONSIBILITY: + * <br> + * - Show Android Emulator Device Instance main information on the UI + * <br> + * COLABORATORS: + * <br> + * AbstractPropertiesComposite: extends this class + * <br> + * USAGE: + * <br> + * This class should be added as a regular composite whenever startup options information on Android Emulator + * Device Instance is necessary to be shown and edited on the UI. + */ +public class StartupOptionsComposite extends AbstractPropertiesComposite implements + IStartupOptionsConstants +{ + // The widget which displays the current command line used to pass the startup options + private Text commandLine; + + private final IAndroidSkin skin; + + private final int TABFOLDER_HEIGHT_HINT = 350; + + private boolean canCalculateScale = true; + + /** + * Creates a StartupOptionsComposite object. + * + * @param parent the parent composite + * @param canCalculateScale + */ + public StartupOptionsComposite(Composite parent, String startupOptions, IAndroidSkin skin, + boolean canCalculateScale) + { + super(parent); + + this.skin = skin; + this.canCalculateScale = canCalculateScale; + StartupOptionsMgt.loadFromCommandLine(startupOptions); + createUI(); + + // Set context Help + PlatformUI.getWorkbench().getHelpSystem() + .setHelp(parent, IAndroidDeviceConstants.STARTUP_OPTIONS_HELP); + } + + /** + * Create widgets for startup options + */ + private void createUI() + { + + Composite mainComposite = this; + Layout mainLayout = new GridLayout(); + mainComposite.setLayout(mainLayout); + + // list of startup options groups + List<StartupOptionsGroup> startupOptionsGroupsList = + StartupOptionsMgt.getStartupOptionsGroupsList(); + + // list of startup options in each group + List<StartupOption> startupOptions = null; + + // Create Tab Folder + final TabFolder tabFolder = new TabFolder(mainComposite, SWT.NULL); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, false); + data.heightHint = TABFOLDER_HEIGHT_HINT; + tabFolder.setLayoutData(data); + + /* + * Iterate through Startup Groups + */ + for (StartupOptionsGroup startupOptionGroup : startupOptionsGroupsList) + { + + // Create Tab Item + TabItem tabItem = new TabItem(tabFolder, SWT.NULL); + tabItem.setText(startupOptionGroup.getTitle()); + Composite group = new Composite(tabFolder, SWT.NULL); + group.setLayout(new GridLayout(3, false)); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + tabItem.setControl(group); + + // get startup options in this group + startupOptions = startupOptionGroup.getStartupOptions(); + + /* + * Iterate through Startup Options in this group + */ + for (final StartupOption startupOption : startupOptions) + { + + // create a checkbox for each startup option + Button checkbox = new Button(group, SWT.CHECK); + checkbox.setSelection(startupOption.isChecked()); + checkbox.setText(startupOption.getUserFriendlyName()); + checkbox.setToolTipText(startupOption.getName() + ": " //$NON-NLS-1$ + + startupOption.getDescription()); + startupOption.setCheckedWidget(checkbox); + checkbox.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + boolean checkedStatus = ((Button) e.widget).getSelection(); + startupOption.setChecked(checkedStatus); + notifyCompositeChangeListeners(); + } + }); + GridData checkboxData = new GridData(SWT.NULL, SWT.FILL, false, false); + checkbox.setLayoutData(checkboxData); + + // Create input fields depending on the startup option type + switch (startupOption.getType()) + { + case TYPE_NONE: + // extend checkbox area along the line + checkboxData.widthHint = SWT.DEFAULT; + checkboxData.horizontalSpan = 3; + checkbox.setLayoutData(checkboxData); + break; + + case TYPE_TEXT: + case TYPE_NUMBER: + createWidgetsForTextOrNumberType(group, startupOption); + break; + + case TYPE_PATH: + createWidgetsForPathType(group, startupOption); + break; + + default: + // none + + } + } + } + + /* + * Command Line area + */ + Composite commandLineArea = new Composite(mainComposite, SWT.NONE); // composite + commandLineArea.setLayout(new GridLayout(2, false)); + data = new GridData(SWT.FILL, SWT.FILL, true, true); + commandLineArea.setLayoutData(data); + + Label commandLineLabel = new Label(commandLineArea, SWT.NONE); // label + commandLineLabel.setText(""); //$NON-NLS-1$ + data = new GridData(SWT.FILL, SWT.FILL, false, true); + commandLineLabel.setLayoutData(data); + + commandLine = new Text(commandLineArea, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL); // text + data = new GridData(SWT.FILL, SWT.FILL, true, true); + commandLineArea.pack(); + data.widthHint = commandLineArea.getBounds().width - commandLineLabel.getBounds().width; + data.heightHint = commandLineArea.getBounds().height; + commandLine.setLayoutData(data); + commandLine.setText(StartupOptionsMgt.getParamList()); + commandLine.setEditable(false); + } + + /** + * Create widgets to enable user to input data for fields of text or number type + * + * @param parent composite where the widgets will be attached to + * @param startupOption the corresponding startup option + */ + private void createWidgetsForTextOrNumberType(final Composite parent, + final StartupOption startupOption) + { + // create input text with calc button + if (startupOption.getName().equals(SCALE)) + { + final Text inputText = new Text(parent, SWT.SINGLE | SWT.BORDER); + inputText.setText(startupOption.getValue()); + startupOption.setValueWidget(inputText); + inputText.setLayoutData(new GridData(SWT.FILL, SWT.NULL, true, false)); + inputText.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + startupOption.setValue(inputText.getText()); + notifyCompositeChangeListeners(); + } + }); + + Button calc = new Button(parent, SWT.PUSH); + calc.setText(EmulatorNLS.UI_DpiScale_Calculator); + GridData calcData = new GridData(SWT.NULL, SWT.NULL, false, false); + calc.setLayoutData(calcData); + calc.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + DpiScaleCalculatorDialog dialog = + new DpiScaleCalculatorDialog(new Shell(parent.getShell()), skin); + if (dialog.open() == Dialog.OK) + { + for (StartupOptionsGroup group : StartupOptionsMgt + .getStartupOptionsGroupsList()) + { + for (StartupOption startupOption : group.getStartupOptions()) + { + if (startupOption.getName().equals(SCALE)) + { + startupOption.setChecked(true); + startupOption.setValue(dialog.getResultScaleValue()); + startupOption.updateUI(); + } + } + } + } + } + }); + calc.setEnabled(canCalculateScale); + if (!canCalculateScale) + { + ControlDecoration controlDecoration = + new ControlDecoration(inputText, SWT.LEFT | SWT.TOP); + controlDecoration + .setDescriptionText(EmulatorNLS.StartupOptionsComposite_Error_Loading_Skin_Cant_Calculate_Scale); + FieldDecoration fieldDecoration = + FieldDecorationRegistry.getDefault().getFieldDecoration( + FieldDecorationRegistry.DEC_WARNING); + controlDecoration.setImage(fieldDecoration.getImage()); + } + } + // create input text + else if ((startupOption.getPreDefinedValues() == null) + || (startupOption.getPreDefinedValues().size() == 0)) + { + final Text inputText = new Text(parent, SWT.SINGLE | SWT.BORDER); + inputText.setText(startupOption.getValue()); + startupOption.setValueWidget(inputText); + inputText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + inputText.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + startupOption.setValue(inputText.getText()); + notifyCompositeChangeListeners(); + } + }); + } + // create combobox + else + { + final Combo combo = new Combo(parent, SWT.READ_ONLY); + startupOption.setValueWidget(combo); + int selectedIndex = 0; + for (String preDefinedValue : startupOption.getPreDefinedValues()) + { + combo.add(preDefinedValue); + if (startupOption.getValue().equals(preDefinedValue)) + { + combo.select(selectedIndex); + } + else + { + selectedIndex++; + } + } + combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + combo.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + startupOption.setValue(combo.getText()); + notifyCompositeChangeListeners(); + } + }); + } + } + + /** + * Create widgets to enable user to input data for fields of path type + * + * @param parent composite where the widgets will be attached to + * @param startupOption the corresponding startup option + */ + private void createWidgetsForPathType(final Composite parent, final StartupOption startupOption) + { + // create input text + final Text pathText = new Text(parent, SWT.SINGLE | SWT.BORDER); + pathText.setText(startupOption.getValue()); + startupOption.setValueWidget(pathText); + pathText.setLayoutData(new GridData(SWT.FILL, SWT.NULL, true, false)); + pathText.addModifyListener(new ModifyListener() + { + public void modifyText(ModifyEvent e) + { + startupOption.setValue(pathText.getText()); + notifyCompositeChangeListeners(); + } + }); + // create browse button + Button pathBrowseButton = new Button(parent, SWT.PUSH); + pathBrowseButton.setText(EmulatorNLS.UI_General_BrowseButtonLabel); + GridData data = new GridData(SWT.NULL, SWT.NULL, false, false); + pathBrowseButton.setLayoutData(data); + pathBrowseButton.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + String selectedPath = null; + + if (startupOption.getTypeDetails().equalsIgnoreCase(TYPE_PATH_DIR)) + { + DirectoryDialog directoryDialog = new DirectoryDialog(getShell(), SWT.OPEN); + selectedPath = directoryDialog.open(); + } + else + { + FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); + String[] filterExtensions = + { + "*" + startupOption.getTypeDetails() //$NON-NLS-1$ + }; + fileDialog.setFilterExtensions(filterExtensions); + selectedPath = fileDialog.open(); + } + + if (selectedPath != null) + { + pathText.setText(selectedPath); + } + } + }); + } + + /** + * Update command line value + * + * @see com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite#notifyCompositeChangeListeners() + */ + @Override + protected void notifyCompositeChangeListeners() + { + commandLine.setText(StartupOptionsMgt.getParamList()); + super.notifyCompositeChangeListeners(); + } + + /** + * Retrieves the error message associated to this composites current state. + * The order of precedence of error is the same as the fields displayed on the + * UI, which means errors on fields drawn first are shown with a higher precedence + * than the errors of fields drawn last. + * The instance description field is the only non required field. + * + * @return the error message, or <code>null</code> if there are no errors + */ + @Override + public String getErrorMessage() + { + String errMsg = null; + Status status = StartupOptionsMgt.validate(); + if (status.getSeverity() == Status.ERROR) + { + errMsg = status.getMessage(); + } + return errMsg; + } + + /** + * Reload the values being displayed in the UI as well as the ones + * in the model. + * + * @param startupOptions commandLine the command line used to start the emulator + */ + public void reloadValues(String commandLine) + { + StartupOptionsMgt.loadFromCommandLine(commandLine); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardMainPage.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardMainPage.java new file mode 100644 index 0000000..35f7457 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardMainPage.java @@ -0,0 +1,445 @@ +/* +* 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.ui.wizard; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Properties; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.sequoyah.device.framework.ui.wizard.DefaultDeviceTypeMenuWizardPage; +import org.eclipse.sequoyah.device.framework.ui.wizard.IInstanceProperties; +import org.eclipse.swt.widgets.Composite; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeEvent; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeListener; +import com.motorola.studio.android.emulator.device.ui.PropertiesMainComposite; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * DESCRIPTION: + * <br> + * This class represents the first wizard page for Android Emulator Device Instance creation. + * <br> + * It shows all main information and validates it, setting an appropriate error message when + * applicable. + * <br> + * RESPONSIBILITY: + * <br> + * - Allow user to enter main information for creating a new Android Emulator Device Instance + * <br> + * - Validate main information entered by user + * <br> + * COLABORATORS: + * <br> + * WizardPage: extends this class + * <br> + * PropertiesMainComposite: uses this composite as the main widget + * <br> + * USAGE: + * <br> + * This wizard page must be added as the first page on the class implementing the New Android + * Emulator Device Instance Wizard. + */ +public class WizardMainPage extends WizardPage implements IInstanceProperties +{ + private PropertiesMainComposite mainComposite; + + DefaultDeviceTypeMenuWizardPage tmlPage = null; + + private PropertyCompositeChangeListener listener = new PropertyCompositeChangeListener() + { + public void compositeChanged(PropertyCompositeChangeEvent e) + { + + String errorMessage = mainComposite.getErrorMessage(); + + if (errorMessage != null) + { + setErrorMessage(errorMessage); + setPageComplete(false); + } + else + { + setErrorMessage(null); + setPageComplete(true); + setMessage(EmulatorNLS.UI_WizardMainPage_PageName); + } + + } + }; + + /** + * Creates a WizardMainPage object. + */ + public WizardMainPage() + { + super(EmulatorNLS.UI_WizardMainPage_PageName); + } + + /** + * Creates the UI for this wizard page. + * It uses the PropertiesMainComposite only. + */ + public void createControl(Composite parent) + { + + // Collecting usage data for statistical purpose + try + { + StudioLogger.collectUsageData(StudioLogger.WHAT_EMULATOR_CREATION_WIZARD, + StudioLogger.KIND_EMULATOR, StudioLogger.DESCRIPTION_DEFAULT, + EmulatorPlugin.PLUGIN_ID, EmulatorPlugin.getDefault().getBundle().getVersion() + .toString()); + } + catch (Throwable e) + { + //Do nothing, but error on the log should never prevent app from working + } + + setTitle(EmulatorNLS.UI_General_WizardTitle); + setErrorMessage(null); + setMessage(EmulatorNLS.UI_WizardMainPage_PageName); + + IAndroidTarget vmTarget = null; + String vmSkin = ""; //$NON-NLS-1$ + String vmPath; + String timeout; + String useVnc; + String useProxy; + String abiType = SdkConstants.ABI_ARMEABI; + String useSnapshot; + String saveSnapshot; + String startFromSnapshot; + + tmlPage = (DefaultDeviceTypeMenuWizardPage) this.getPreviousPage(); + + IAndroidTarget targets[] = SdkUtils.getAllTargets(); + + if ((targets != null) && (targets.length > 0)) + { + + // Sort the targets array by comparing the API level of them + Arrays.sort(targets, new Comparator<IAndroidTarget>() + { + + public int compare(IAndroidTarget o1, IAndroidTarget o2) + { + int returnValue; + if (o1.getVersion().getApiLevel() == o2.getVersion().getApiLevel()) + { + returnValue = 0; + } + else if (o1.getVersion().getApiLevel() > o2.getVersion().getApiLevel()) + { + returnValue = 1; + } + else + { + returnValue = -1; + } + + return returnValue; + } + + }); + + // Gets the first target with the highest API + + int maxAPILevel = targets[targets.length - 1].getVersion().getApiLevel(); + + for (IAndroidTarget t : targets) + { + if (t.getVersion().getApiLevel() == maxAPILevel) + { + vmTarget = t; + break; + } + + } + + String skins[] = vmTarget.getSkins(); + vmSkin = vmTarget.getDefaultSkin(); + List<String> skinsList = Arrays.asList(skins); + + /* + * Workaround to select WVGA skin on JIL SDK because HVGA skin is broken + */ + if (SdkUtils.isJILSdk()) + { + String tmpVmSkin = null; + int i = 0; + while ((tmpVmSkin == null) && (i < skins.length)) + { + if (skins[i].toLowerCase().trim().equals("wvga")) + { + tmpVmSkin = skins[i]; + } + i++; + } + if (tmpVmSkin != null) + { + vmSkin = tmpVmSkin; + } + } + + if (!skinsList.contains(vmSkin)) + { + vmSkin = skins[0]; + } + } + + vmPath = IDevicePropertiesConstants.defaultVmPath; + + // get the default properties value + Properties defaultProperties = new Properties(); + AndroidDeviceInstance.populateWithDefaultProperties(defaultProperties); + timeout = defaultProperties.getProperty(IDevicePropertiesConstants.timeout); + useVnc = defaultProperties.getProperty(IDevicePropertiesConstants.useVnc); + useProxy = defaultProperties.getProperty(IDevicePropertiesConstants.useProxy); + useSnapshot = defaultProperties.getProperty(IDevicePropertiesConstants.useSnapshots); + saveSnapshot = defaultProperties.getProperty(IDevicePropertiesConstants.saveSnapshot); + startFromSnapshot = defaultProperties.getProperty(IDevicePropertiesConstants.startFromSnapshot); + + // 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 + mainComposite = + new PropertiesMainComposite(parent, tmlPage.getInstanceName(), + EmulatorPlugin.DEFAULT_EMULATOR_DEFINITION, timeout, + Boolean.parseBoolean(useVnc), Boolean.parseBoolean(useProxy), + Boolean.parseBoolean(useSnapshot), Boolean.parseBoolean(saveSnapshot), + Boolean.parseBoolean(startFromSnapshot), vmTarget, vmSkin, vmPath, abiType, + false, true, true); + + AbstractPropertiesComposite.addCompositeChangeListener(listener); + + setControl(mainComposite); + + if ((targets == null) || ((targets != null) && (targets.length <= 0))) + { + setMessage(EmulatorNLS.WizardMainPage_NO_SDK_CONFIGURED_MSG); + setPageComplete(false); + return; + } + + String initialMessage = mainComposite.getErrorMessage(); + + if (initialMessage != null) + { + setMessage(initialMessage); + } + setPageComplete(initialMessage == null); + } + + @Override + public void setVisible(boolean visible) + { + if (visible) + { + mainComposite.setName(tmlPage.getInstanceName()); + } + super.setVisible(visible); + } + + /** + * Retrieves the skin id associated with this instance + * + * @return the skin id + */ + public String getSkinId() + { + return mainComposite.getSkinId(); + } + + /** + * Retrieves the timeout associated with this instance + * + * @return the timeout + */ + public String getTimeout() + { + return mainComposite.getTimeout(); + } + + /** + * Retrieves the vnc option associated with this instance + * + * @return the timeout + */ + public String getUseVnc() + { + return mainComposite.getUseVnc(); + } + + /** + * Retrieves the proxy option associated with this instance + * + * @return the timeout + */ + public String getUseProxy() + { + return mainComposite.getUseProxy(); + } + + /** + * Retrieves the emulator definition name associated with this instance + * + * @return the emulator definition name + */ + public String getEmulatorDefId() + { + // 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 + return EmulatorPlugin.DEFAULT_EMULATOR_DEFINITION; + } + + public String getUseSnapshot() + { + return mainComposite.getUseSnapshot(); + } + + public void setInstanceName(String name) + { + mainComposite.setName(name); + } + + /** + * Retrieves VM target + */ + public IAndroidTarget getVmTarget() + { + return mainComposite.getVmTarget(); + } + + /** + * Retrieves VM target + */ + public String getAbiType() + { + return mainComposite.getAbiType(); + } + + /** + * Retrieves VM skin + */ + public String getVmSkin() + { + return mainComposite.getVmSkin(); + } + + /** + * Retrieves VM path + */ + public String getVmPath() + { + return mainComposite.getVmPath(); + } + + /** + * Retrieves SD Card info + */ + public String getSDCard() + { + return mainComposite.getSDCard(); + } + + /** + * Retrieves Command line + */ + public String getCommandLine() + { + //The command line shall be editable through the GUI + Properties defaultProperties = new Properties(); + AndroidDeviceInstance.populateWithDefaultProperties(defaultProperties); + return defaultProperties.getProperty(IDevicePropertiesConstants.commandline); + } + + @Override + public boolean isPageComplete() + { + return (mainComposite != null) && (mainComposite.getErrorMessage() == null); + } + + @Override + public void dispose() + { + AbstractPropertiesComposite.removeCompositeChangeListener(listener); + setControl(null); + if (mainComposite != null) + { + mainComposite.dispose(); + mainComposite = null; + } + + super.dispose(); + } + + public Properties getProperties() + { + Properties properties = new Properties(); + + properties.setProperty(IDevicePropertiesConstants.timeout, this.getTimeout()); + properties.setProperty(IDevicePropertiesConstants.useVnc, this.getUseVnc()); + properties.setProperty(IDevicePropertiesConstants.useProxy, this.getUseProxy()); + properties.setProperty(IDevicePropertiesConstants.emulatorDefId, this.getEmulatorDefId()); + properties.setProperty(IDevicePropertiesConstants.skinId, this.getSkinId()); + properties.setProperty(IDevicePropertiesConstants.vmSkin, this.getVmSkin()); + properties.setProperty(IDevicePropertiesConstants.vmPath, this.getVmPath()); + properties.setProperty(IDevicePropertiesConstants.abiType, this.getAbiType()); + properties.setProperty(IDevicePropertiesConstants.useSnapshots, this.getUseSnapshot()); + properties.setProperty(IDevicePropertiesConstants.saveSnapshot, this.getSaveSnapshot()); + properties.setProperty(IDevicePropertiesConstants.startFromSnapshot, this.getstartFromSnapshot()); + + if (this.getVmTarget() != null) + { + properties.setProperty(IDevicePropertiesConstants.vmTarget, this.getVmTarget() + .getName()); + } + else + { + properties.setProperty(IDevicePropertiesConstants.vmTarget, ""); //$NON-NLS-1$ + } + + return properties; + } + + /** + * @return + */ + public String getstartFromSnapshot() + { + return mainComposite.getstartFromSnapshot(); + } + + /** + * @return + */ + public String getSaveSnapshot() + { + return mainComposite.getSaveSnapshot(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardMainPageOperation.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardMainPageOperation.java new file mode 100644 index 0000000..772684c --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardMainPageOperation.java @@ -0,0 +1,76 @@ +/* +* 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.ui.wizard; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.sequoyah.device.framework.ui.wizard.DefaultDeviceTypeMenuWizardPage; +import org.eclipse.sequoyah.device.framework.ui.wizard.DeviceWizardRunnable; + +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.device.refresh.InstancesListRefresh; + +/** + * This class performs the wizard finish operation for the WizardMainPage, + * according to the extension point + * org.eclipse.sequoyah.device.framework.ui.newDeviceWizardPages + */ +public class WizardMainPageOperation extends DeviceWizardRunnable +{ + /** + * Action executed on the wizard finish + */ + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException + { + // Get wizard pages + WizardMainPage page = (WizardMainPage) this.getWizardPage(); + DefaultDeviceTypeMenuWizardPage tmlPage = + (DefaultDeviceTypeMenuWizardPage) page.getPreviousPage(); + + // Create VM + try + { + //TML should provide some instance name changed listener + if (!tmlPage.getInstanceName().equals(page.getName())) + { + page.setInstanceName(tmlPage.getInstanceName()); + } + + SdkUtils.createVm(page.getVmPath(), tmlPage.getInstanceName(), page.getVmTarget(), page + + .getAbiType(), page.getVmSkin(), page.getUseSnapshot(), (page.getSDCard().length() == 0 + ? null : page.getSDCard())); + + } + catch (CoreException e) + { + EclipseUtils.showErrorDialog("Could not create instance ", e.getStatus().getMessage()); + + StudioLogger.error(WizardMainPageOperation.class, + "Could not create AVD: " + tmlPage.getInstanceName(), e); + } + + Collection<String> vmInstances = SdkUtils.getAllValidVmNames(); + InstancesListRefresh.refreshStatus(vmInstances, tmlPage.getInstanceName()); + + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardStartupOptionsPage.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardStartupOptionsPage.java new file mode 100644 index 0000000..02bbc97 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/device/ui/wizard/WizardStartupOptionsPage.java @@ -0,0 +1,195 @@ +/* +* 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.ui.wizard; + +import java.io.File; +import java.util.Properties; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.sequoyah.device.framework.ui.wizard.IInstanceProperties; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.SkinFramework; +import com.motorola.studio.android.emulator.device.IDevicePropertiesConstants; +import com.motorola.studio.android.emulator.device.instance.options.StartupOptionsMgt; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeEvent; +import com.motorola.studio.android.emulator.device.ui.AbstractPropertiesComposite.PropertyCompositeChangeListener; +import com.motorola.studio.android.emulator.device.ui.StartupOptionsComposite; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * DESCRIPTION: + * <br> + * This class represents the second wizard page for Android Emulator Device Instance creation. + * <br> + * It shows all startup options information and validates it, setting an appropriate error message when + * applicable. + * <br> + * RESPONSIBILITY: + * <br> + * - Allow user to enter startup options information for creating a new Android Emulator Device Instance + * <br> + * - Validates tartup options information entered by user + * <br> + * COLABORATORS: + * <br> + * WizardPage: extends this class + * <br> + * StartupOptionsMainComposite: uses this composite as the main widget + * <br> + * USAGE: + * <br> + * This wizard page must be added as the second page on the class implementing the New Android + * Emulator Device Instance Wizard. + */ +public class WizardStartupOptionsPage extends WizardPage implements IInstanceProperties +{ + private StartupOptionsComposite startupOptionsComposite; + + private IAndroidSkin skin; + + // handle changes + private final PropertyCompositeChangeListener compositeChangeListener = + new PropertyCompositeChangeListener() + { + public void compositeChanged(PropertyCompositeChangeEvent e) + { + String errorMessage = startupOptionsComposite.getErrorMessage(); + if (errorMessage != null) + { + setErrorMessage(errorMessage); + setPageComplete(false); + } + else + { + setErrorMessage(null); + setPageComplete(true); + } + } + }; + + /** + * Creates a WizardMainPage object. + */ + public WizardStartupOptionsPage() + { + super(EmulatorNLS.UI_WizardStartupOptionsPage_PageMessage); + + } + + /** + * Creates the UI for this wizard page. + * It uses the PropertiesMainComposite only. + */ + public void createControl(Composite parent) + { + // Get selected Skin name + WizardMainPage page = (WizardMainPage) this.getPreviousPage(); + boolean canCalculateScale = true; + try + { + if (page.getSkinId() != null) + { + SkinFramework sm = new SkinFramework(); + skin = + sm.getSkinById(page.getSkinId(), new File(page.getVmTarget().getLocation() + + "skins" + File.separator + page.getVmSkin())); + } + } + catch (SkinException e) + { + StudioLogger.error(this.getClass(), + "Error reading instance skin during startup options page creation", e); + canCalculateScale = false; + } + + setTitle(EmulatorNLS.UI_General_WizardTitle); + setErrorMessage(null); + if (getMessage() != null) + { + setMessage(EmulatorNLS.UI_WizardStartupOptionsPage_PageMessage); + } + + // Define layout + GridLayout mainLayout = new GridLayout(1, false); + mainLayout.marginTop = 0; + mainLayout.marginWidth = 0; + mainLayout.marginHeight = 0; + + // Create Startup Options area + startupOptionsComposite = + new StartupOptionsComposite(parent, NativeUIUtils.getDefaultCommandLine(), skin, + canCalculateScale); + + AbstractPropertiesComposite.addCompositeChangeListener(compositeChangeListener); + + // Set layout + startupOptionsComposite.setLayout(mainLayout); + + setControl(startupOptionsComposite); + + setPageComplete(true); + } + + @Override + public boolean isPageComplete() + { + boolean isComplete = true; + if (startupOptionsComposite != null) + { + isComplete = (startupOptionsComposite.getErrorMessage() == null); + } + return isComplete; + } + + @Override + public void dispose() + { + AbstractPropertiesComposite.removeCompositeChangeListener(compositeChangeListener); + setControl(null); + if (startupOptionsComposite != null) + { + startupOptionsComposite.dispose(); + startupOptionsComposite = null; + } + super.dispose(); + } + + public Properties getProperties() + { + Properties properties = new Properties(); + + if (startupOptionsComposite == null) + { + properties.setProperty(IDevicePropertiesConstants.commandline, + NativeUIUtils.getDefaultCommandLine()); + + } + else + { + properties.setProperty(IDevicePropertiesConstants.commandline, + StartupOptionsMgt.getParamList()); + } + + return properties; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/i18n/EmulatorNLS.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/i18n/EmulatorNLS.java new file mode 100644 index 0000000..dc95c9c --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/i18n/EmulatorNLS.java @@ -0,0 +1,428 @@ +/* +* 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.i18n; + +import org.eclipse.osgi.util.NLS; + +/** + * DESCRIPTION: + * This class is the NLS component for Emulator Core plugin. + * It is the main class for internationalization + * + * RESPONSIBILITY: + * Provide local strings for using throughout the tool + * + * COLABORATORS: + * coremessages.properties: file that contains the strings that will be provided + * to the plugin + * + * USAGE: + * Use any of the public static variables for accessing the local strings + */ +public class EmulatorNLS extends NLS +{ + + /** + * The bundle location. + * It refers to messages.properties file inside this package + */ + + static + { + NLS.initializeMessages("com.motorola.studio.android.emulator.i18n.emulatorNLS", + EmulatorNLS.class); + } + + /* + * Generic string area + */ + + public static String GEN_Error; + + public static String GEN_Warning; + + public static String GEN_Question; + + /* + * Exception string area + */ + + public static String EXC_SkinFramework_CreateIAndroidSkin; + + /* + * Warning string area + */ + + public static String WARN_SkinFramework_SkinNotInstalled; + + public static String WARN_SkinFramework_InvalidInstalledSkinsNotLoaded; + + /* + * Error string area + */ + + public static String ERR_SrcDestComposite_InvalidFillingBase; + + public static String ERR_SrcDestComposite_InvalidFillingPhoneNumber; + + public static String ERR_SrcDestComposite_InvalidFillingEmulator; + + /* + * Information string area + */ + + /* + * Question string area + */ + + /* + * UI string area + */ + + public static String UI_SrcDestComposite_OriginatingRunningEmulatorLabel; + + public static String UI_SrcDestComposite_DestinationRunningEmulatorLabel; + + public static String UI_SrcDestComposite_OriginatingPhoneNumberLabel; + + public static String UI_SrcDestComposite_DestinationPhoneNumberLabel; + + /* + * Exception string area + */ + + public static String EXC_AndroidEmulatorStarter_TimeoutWhileRunningProtocol; + + public static String EXC_AndroidEmulatorStarter_EmulatorStartCanceled; + + public static String EXC_AndroidEmulatorReseter_ErrorWhilePerformingDeleteOperation; + + public static String EXC_AndroidEmulatorReseter_ErrorWhilePerformingSnapshotCopyOperation; + + public static String EXC_AndroidEmulatorReseter_ErrorWhilePerformingDeleteSnapshotOperation; + + public static String EXC_AndroidEmulatorStarter_ProcessTerminated; + + public static String EXC_TimeoutWhileStarting; + + public static String EXC_VncServerNotRunning; + + public static String EXC_CouldNotStartProtocol; + + public static String EXC_AndroidExceptionHandler_CannotRunStopService; + + public static String EXC_AndroidLogicUtils_CannotStartProcess; + + public static String EXC_AndroidLogicUtils_DeviceIsOffline; + + /* + * Error string area + */ + + public static String ERR_AndroidEmulatorStarter_InstanceNullPointer; + + public static String ERR_AndroidEmulatorStarter_NoLogicAvailableForStart; + + public static String ERR_AndroidLogicPlugin_EmulatorStopped; + + public static String ERR_AndroidLogicPlugin_InvalidTimeoutValue; + + public static String ERR_TransferFilesLogic_NotEnoughInformation; + + /* + * Question string area + */ + + public static String QUESTION_AndroidEmulatorReseter_ConfirmationText; + + public static String QUESTION_AndroidEmulatorStopper_StopEmulatorQuestion; + + public static String QUESTION_AndroidExceptionHandler_ImpossibleToReconnect; + + public static String QUESTION_AndroidEmulatorReseter_Yes; + + public static String QUESTION_AndroidEmulatorReseter_No; + + /* + * Information string area + */ + + public static String INFO_ConnectVncLogic_UserCancelledVncServerStart; + + /* + * Progress monitor string area + */ + + public static String MON_AndroidEmulatorStarter_ConnectingToEmulator; + + public static String MON_AndroidEmulatorStopper_DisposingInstance; + + public static String MON_AndroidEmulatorStopper_StopVm; + + public static String MON_AndroidEmulatorStarter_Canceling; + + public static String DPISCALECALCULATOR_Error_MonitorDpi; + + public static String DPISCALECALCULATOR_Error_MonitorSize; + + public static String DPISCALECALCULATOR_Error_ScreenSize; + + public static String DPISCALECALCULATOR_MonitorDpi_Label; + + public static String DPISCALECALCULATOR_MonitorDpiSize_Label; + + public static String DPISCALECALCULATOR_MonitorDpivalue_Label; + + public static String DPISCALECALCULATOR_Regex_TwoDigits; + + public static String DPISCALECALCULATOR_ResultGroup_Title; + + public static String DPISCALECALCULATOR_ResultMonitorDpi_Label; + + public static String DPISCALECALCULATOR_ResultScale_Label; + + public static String DPISCALECALCULATOR_ScreenSize_Label; + + public static String DPISCALECALCULATOR_Title; + + /* + * Error string area + */ + public static String ERR_PropertiesMainComposite_MissingTimeoutValue; + + public static String ERR_PropertiesMainComposite_TimeoutValueIsNotPositiveInteger; + + public static String ERR_PropertiesMainComposite_MissingSDCardPath; + + public static String ERR_PropertiesMainComposite_MissingSDCardSize; + + public static String ERR_PropertiesMainComposite_SDCardPathIsNotValid; + + public static String ERR_PropertiesMainComposite_SDCardSizeIsNotPositiveInteger; + + public static String ERR_PropertiesMainComposite_ABINotAvailable; + + // Startup options - all + public static String ERR_PropertiesMainComposite_StartupOpt_NoQuotes; + + // Startup options - text + public static String ERR_PropertiesMainComposite_StartupOpt_TextBlank; + + // Startup options - number + public static String ERR_PropertiesMainComposite_StartupOpt_NumberRequired; + + public static String ERR_PropertiesMainComposite_StartupOpt_NumberMustBeInteger; + + public static String ERR_PropertiesMainComposite_StartupOpt_NumberMustBePositiveInteger; + + public static String ERR_PropertiesMainComposite_StartupOpt_NumberIntRange; + + // Startup options - path + public static String ERR_PropertiesMainComposite_StartupOpt_PathRequired; + + public static String ERR_PropertiesMainComposite_StartupOpt_PathDirNotExist; + + public static String ERR_PropertiesMainComposite_StartupOpt_PathMustBeDir; + + public static String ERR_PropertiesMainComposite_StartupOpt_PathFileNotExist; + + public static String ERR_PropertiesMainComposite_StartupOpt_PathMustBeFile; + + public static String ERR_PropertiesMainComposite_StartupOpt_PathIncorrectFileType; + + /* + * Info string area + */ + public static String INFO_InfoComposite_EmulatorDefinitionNotFound; + + /* + * UI string area + */ + public static String UI_General_BrowseButtonLabel; + + public static String UI_General_WizardTitle; + + public static String UI_PropertiesMainComposite_NameLabel; + + public static String UI_PropertiesMainComposite_EmulatorWindowMode_GroupTitle; + + public static String UI_PropertiesMainComposite_EmulatorWindowMode_NativeLabel; + + public static String UI_PropertiesMainComposite_EmulatorWindowMode_VncLabel; + + public static String UI_PropertiesMainComposite_TimeoutLabel; + + public static String UI_WizardMainPage_PageName; + + public static String UI_WizardStartupOptionsPage_PageMessage; + + public static String UI_AndroidDeviceInstance_StopInstanceJob; + + public static String UI_DpiScale_Calculator; + + /* + * Wizard - VM area + */ + public static String UI_PropertiesMainComposite_TargetLabel; + + public static String UI_PropertiesMainComposite_SkinLabel; + + public static String UI_PropertiesMainComposite_PathLabel; + + public static String UI_PropertiesMainComposite_SDCardLabel; + + public static String UI_PropertiesMainComposite_SDCardNoneLabel; + + public static String UI_PropertiesMainComposite_SDCardExistingLabel; + + public static String UI_PropertiesMainComposite_SDCardNewLabel; + + public static String UI_PropertiesMainComposite_SDCardPathLabel; + + public static String UI_PropertiesMainComposite_SDCardSizeLabel; + + public static String UI_PropertiesMainComposite_PathGroupTitle; + + public static String UI_PropertiesMainComposite_UseDefaultPath; + + /* + * Wizard - VM area errors + */ + public static String ERR_PropertiesMainComposite_VmTargetEmpty; + + public static String ERR_PropertiesMainComposite_VmSkinEmpty; + + public static String ERR_PropertiesMainComposite_VmPathInvalid; + + /* + * Question string area + */ + public static String WizardMainPage_NO_SDK_CONFIGURED_MSG; + + public static String UI_SdkSetup_CreateAVD_Title; + + public static String UI_SdkSetup_CreateAVD_Message; + + /* + * Exception string area + */ + + public static String EXC_General_CannotRunStopService; + + public static String EXC_AncroidView_CannotRunMultipleStopServices; + + public static String EXC_AndroidView_ErrorStartingScreens; + + public static String EXC_AbstractZoomHandler_InstanceNotFound; + + public static String EXC_AndroidView_ViewNotFound; + + /* + * Warning string area + */ + + /* + * Error string area + */ + + public static String ERR_AndroidView_ProtocolImplementerNotSupported; + + public static String EXC_AbstractAndroidView_ViewNotAccessibleProgramatically; + + /* + * Information string area + */ + + /* + * Question string area + */ + + public static String QUESTION_AndroidView_StopAllInstancesOnDisposeTitle; + + public static String QUESTION_AndroidView_StopAllInstancesOnDisposeMessage; + + public static String QUESTION_AbstractAndroidView_OpenViewForStartedEmulatorsTitle; + + public static String QUESTION_AbstractAndroidView_OpenViewForStartedEmulatorsMessage; + + public static String QUESTION_RunningInstancesOnClose_Title; + + public static String QUESTION_RunningInstancesOnClose_Text; + + public static String QUESTION_NativeWindow_LooseOriginalScale_Title; + + public static String QUESTION_NativeWindow_LooseOriginalScale_Text; + + /* + * Warn string area + */ + public static String WARN_RunningInstancesOnClose_Linux_Title; + + public static String WARN_RunningInstancesOnClose_Linux_Text; + + /* + * UI string area + */ + + public static String UI_AbstractAndroidView_StopInstanceJob; + + public static String UI_LayoutContributionItem_NoLayoutsAvailable; + + /* + * Progress monitor string area + */ + public static String ERR_CannotConnectToVNC; + + public static String ERR_StopEmulatorHandler_NotAnAndroidEmulator; + + public static String ERR_StartEmulatorHandler_NotAnAndroidEmulator; + + public static String ERR_AndroidSkinTranslator_ErrorReadingKeycodeFile; + + public static String ERR_AndroidSkin_NoLayoutLoaded; + + public static String ERR_AndroidSkin_ProvidedSkinPathIsNotADirectory; + + public static String ERR_AndroidSkin_InvalidLayoutProvided; + + public static String ERR_LayoutFileParser_BracketsDoNotMatch; + + public static String ERR_LayoutFileParser_LayoutFileCouldNotBeRead; + + public static String PropertiesMainComposite_ABITypeLabel; + + public static String PropertiesMainComposite_ProxySettings_CheckboxLabel; + + public static String PropertiesMainComposite_ProxySettings_GroupTitle; + + public static String PropertiesMainComposite_ProxySettings_LinkToPreference; + + public static String PropertiesMainComposite_SaveSnapshot; + + public static String PropertiesMainComposite_SDCard_Size_Invalid_Integer; + + public static String PropertiesMainComposite_SnapshotSettings; + + public static String PropertiesMainComposite_startFromSnapshot; + + public static String PropertiesMainComposite_UseSnapshot; + + public static String RepairAvdHandler_AVD_NOT_REPAIRABLE; + + public static String RepairAvdHandler_Not_Android_Instance; + + public static String StartupOptionsComposite_Error_Loading_Skin_Cant_Calculate_Scale; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/i18n/emulatorNLS.properties b/src/plugins/emulator/src/com/motorola/studio/android/emulator/i18n/emulatorNLS.properties new file mode 100644 index 0000000..44527f9 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/i18n/emulatorNLS.properties @@ -0,0 +1,173 @@ +# +# 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. +# + +GEN_Error=Error +GEN_Warning=Warning +GEN_Question=Question +EXC_SkinFramework_CreateIAndroidSkin=There was an internal error while loading the {0} skin.\nAn application restart may solve this problem, but if it recurs, you may need to reinstall MOTODEV Studio. +WARN_SkinFramework_SkinNotInstalled=The skin named {0} is not installed.\nAlthough this does not prevent the Android emulator from working, this skin is not accessible.\nIf this error recurs, you may need to reinstall MOTODEV Studio. +WARN_SkinFramework_InvalidInstalledSkinsNotLoaded=Skin {0} is invalid and was not loaded.\nAlthough this does not prevent the Android emulator from working, this skin is not accessible.\nIf this error recurs, you may need to reinstall MOTODEV Studio. +ERR_SrcDestComposite_InvalidFillingBase=Errors were found. Check the following:\n{0}{1} +ERR_SrcDestComposite_InvalidFillingPhoneNumber=\n\t- In the phone number field, be sure you have entered only digits, with no additional symbols such as hyphens, parentheses, or spaces. +ERR_SrcDestComposite_InvalidFillingEmulator=\n\t- In the emulator list, be sure you have selected a running emulator. +UI_SrcDestComposite_OriginatingRunningEmulatorLabel=Originating Android Emulator : +UI_SrcDestComposite_DestinationRunningEmulatorLabel=Destination Android Emulator : +UI_SrcDestComposite_OriginatingPhoneNumberLabel=Originating Phone # : +UI_SrcDestComposite_DestinationPhoneNumberLabel=Destination Phone # : +EXC_AndroidEmulatorStarter_TimeoutWhileRunningProtocol=An error is preventing the Android Emulator from being displayed. \ + Some possible causes are:\n\t- The timeout interval is too short. You may need to increase the timeout value on \ + the Android Virtual Device property page.\n\nSince the emulator is not fully functional, MOTODEV Studio is \ + aborting the emulator start process. +EXC_AndroidEmulatorStarter_EmulatorStartCanceled=Android Emulator start process canceled by user. +EXC_AndroidEmulatorReseter_ErrorWhilePerformingDeleteOperation=Reset operation could not be completed. Manually remove all \ + files, except config.ini and snapshots.img (if it exists), from {0} to complete the process. If you don't have the needed permissions, contact your system administrator. +EXC_AndroidEmulatorReseter_ErrorWhilePerformingDeleteSnapshotOperation=Reset operation could not be completed. Manually remove all \ + files, except config.ini from {0} to complete the process. If the snapshots.img file exists, replace it by {1}. If you don't have the needed permissions, contact your system administrator. +EXC_AndroidEmulatorReseter_ErrorWhilePerformingSnapshotCopyOperation=Error while resetting the snapshot file. Manually copy the file from {0} \ + to {1} to complete the process. If you don't have the needed permissions, contact your system administrator. +EXC_AndroidEmulatorStarter_ProcessTerminated=The emulator process terminated unexpectedly: +EXC_TimeoutWhileStarting=The timeout was reached while trying to start Android Emulator {0}. +EXC_VncServerNotRunning=The VNC server is not running on {0}.\n{1} +EXC_CouldNotStartProtocol=Couldn't establish a connection to the Android emulator. +EXC_AndroidExceptionHandler_CannotRunStopService=The device instance could not be stopped. +EXC_AndroidLogicUtils_CannotStartProcess=It was not possible to start the emulator process. The instance start process will be aborted. +EXC_AndroidLogicUtils_DeviceIsOffline=The device is not online. +ERR_AndroidEmulatorStarter_InstanceNullPointer=Android emulator was not defined. The operation will not be performed. +ERR_AndroidEmulatorStarter_NoLogicAvailableForStart=An error is preventing the Android emulator from being started. \ + Try reinstalling or updating MOTODEV Studio. +ERR_AndroidLogicPlugin_EmulatorStopped=The Android emulator process has unexpectedly stopped running. The instance {0} is now stopped. +ERR_AndroidLogicPlugin_InvalidTimeoutValue=Invalid timeout value: {0} +ERR_TransferFilesLogic_NotEnoughInformation=There is not enough information about the files being transferred to the emulator. +INFO_ConnectVncLogic_UserCancelledVncServerStart=User canceled the execution of the VNC server! +QUESTION_AndroidEmulatorReseter_ConfirmationText=WARNING: Resetting the emulator(s) will delete all user-specific data, such as installed applications, custom files (such as user databases) and any customizations performed on the device(s).\n\nDo you want to proceed? +QUESTION_AndroidEmulatorStopper_StopEmulatorQuestion=Are you sure you want to stop "{0}"? +QUESTION_AndroidExceptionHandler_ImpossibleToReconnect=Cannot reconnect to the emulator. Do you want to retry? +MON_AndroidEmulatorStarter_ConnectingToEmulator=Connecting to Android emulator instance... +MON_AndroidEmulatorStopper_DisposingInstance=Disposing of the Android emulator instance +MON_AndroidEmulatorStopper_StopVm=Stopping the device instance. This may take a few minutes... +MON_AndroidEmulatorStarter_Canceling=Canceling +QUESTION_AndroidEmulatorReseter_Yes=Yes +QUESTION_AndroidEmulatorReseter_No=No +DPISCALECALCULATOR_Error_MonitorDpi=Monitor Dpi value should contain only digits +DPISCALECALCULATOR_Error_MonitorSize=Monitor Size should contain only digits and dots +DPISCALECALCULATOR_Error_ScreenSize=Screen size must contain only digits and dots +DPISCALECALCULATOR_MonitorDpi_Label=Monitor Screen Resolution +DPISCALECALCULATOR_MonitorDpiSize_Label=Based on Monitor Size (in) +DPISCALECALCULATOR_MonitorDpivalue_Label=Value (dpi) +DPISCALECALCULATOR_Regex_TwoDigits=\\d+(\\.\\d{1,2})* +DPISCALECALCULATOR_ResultGroup_Title=Result +DPISCALECALCULATOR_ResultMonitorDpi_Label=Resolution (dpi): +DPISCALECALCULATOR_ResultScale_Label=Scale: +DPISCALECALCULATOR_ScreenSize_Label=Device Screen Size(in) : +DPISCALECALCULATOR_Title=Screen Resolution / Scale Calculator +ERR_PropertiesMainComposite_MissingTimeoutValue=You must provide a timeout value +ERR_PropertiesMainComposite_TimeoutValueIsNotPositiveInteger=The timeout value must be a positive integer +ERR_PropertiesMainComposite_MissingSDCardPath=You must provide a valid SD Card path +ERR_PropertiesMainComposite_MissingSDCardSize=You must provide the SD Card size +ERR_PropertiesMainComposite_SDCardPathIsNotValid=The SD Card path is not valid +ERR_PropertiesMainComposite_SDCardSizeIsNotPositiveInteger=The SD Card size must be greater than 9MB +INFO_InfoComposite_EmulatorDefinitionNotFound=The selected emulator type was not found.\ + The device was not created.\ + The default type has been selected instead. +UI_General_BrowseButtonLabel=Browse... +UI_General_WizardTitle=New Android Virtual Device Instance +UI_PropertiesMainComposite_NameLabel=Name : +UI_PropertiesMainComposite_TimeoutLabel=Timeout (sec): +UI_PropertiesMainComposite_EmulatorWindowMode_GroupTitle=Internal Emulator Window +UI_PropertiesMainComposite_EmulatorWindowMode_NativeLabel=Show the native Emulator window within an Eclipse view (recommended) +UI_PropertiesMainComposite_EmulatorWindowMode_VncLabel=Use VNC to show the Emulator within an Eclipse view +UI_WizardMainPage_PageName=Enter Android Virtual Device Instance Main Information +UI_WizardStartupOptionsPage_PageMessage=Enter the startup options you want the Android Virtual Device instance to use. +UI_AndroidDeviceInstance_StopInstanceJob=Stop Device Instance +UI_DpiScale_Calculator=Calculate +UI_PropertiesMainComposite_TargetLabel=AVD Target : +UI_PropertiesMainComposite_SkinLabel=AVD Skin : +UI_PropertiesMainComposite_PathGroupTitle=AVD Path +UI_PropertiesMainComposite_PathLabel=AVD Path : +UI_PropertiesMainComposite_SDCardLabel=SD Card +UI_PropertiesMainComposite_SDCardNoneLabel=None +UI_PropertiesMainComposite_SDCardExistingLabel=Existing +UI_PropertiesMainComposite_SDCardNewLabel=New +UI_PropertiesMainComposite_SDCardPathLabel=SD Card Path : +UI_PropertiesMainComposite_SDCardSizeLabel=SD Card Size : +UI_PropertiesMainComposite_UseDefaultPath= Use default +ERR_PropertiesMainComposite_VmTargetEmpty=You must provide a target for the AVD +ERR_PropertiesMainComposite_VmSkinEmpty=You must provide a skin for the AVD +ERR_PropertiesMainComposite_VmPathInvalid=You must provide a valid and existing path for the AVD +ERR_PropertiesMainComposite_StartupOpt_NoQuotes=The value for {0} contains quotes, which are not allowed. +ERR_PropertiesMainComposite_StartupOpt_TextBlank=Provide a value for {0}. +ERR_PropertiesMainComposite_StartupOpt_NumberRequired=Provide a value for {0}. +ERR_PropertiesMainComposite_StartupOpt_NumberMustBeInteger=The value for {0} must be an integer. +ERR_PropertiesMainComposite_StartupOpt_NumberMustBePositiveInteger=The value for {0} must be a positive integer. +ERR_PropertiesMainComposite_StartupOpt_NumberIntRange=The value for {0} must be a value between {1} and {2}. +ERR_PropertiesMainComposite_StartupOpt_PathRequired=Provide a path for {0}. +ERR_PropertiesMainComposite_StartupOpt_PathDirNotExist=The directory specified for {0} doesn't exist. Specify a valid directory. +ERR_PropertiesMainComposite_StartupOpt_PathMustBeDir=Select a folder for {0}, not a file. +ERR_PropertiesMainComposite_StartupOpt_PathFileNotExist=The file specified for {0} doesn't exist. Specify a valid path. +ERR_PropertiesMainComposite_StartupOpt_PathMustBeFile=Select a file for {0}, not a folder. +ERR_PropertiesMainComposite_StartupOpt_PathIncorrectFileType=The file specified for {0} is not a {1} file. Specify a valid path. +WizardMainPage_NO_SDK_CONFIGURED_MSG=Configure an SDK before creating an AVD. + +UI_SdkSetup_CreateAVD_Title = Create AVD +UI_SdkSetup_CreateAVD_Message = A valid AVD (Android Virtual Device) was not detected. Do you want to create one? +EXC_General_CannotRunStopService=The instance could not be stopped automatically. +EXC_AncroidView_CannotRunMultipleStopServices=One or more instances could not be stopped automatically. +EXC_AndroidView_ErrorStartingScreens=There was an error while trying to refresh the emulator screen. \ + Restart the emulator to refresh the screen. +EXC_AbstractZoomHandler_InstanceNotFound=The currently displayed Android emulator instance is no longer \ + available.\nThe view is being closed to reflect this. +EXC_AndroidView_ViewNotFound=The Emulator view could not be accessed.\nThe requested operation will not \ + be performed. +ERR_AndroidView_ProtocolImplementerNotSupported=The device instance is not using a communication protocol \ + supported by this viewer. The instance will be stopped. +QUESTION_AndroidView_StopAllInstancesOnDisposeTitle=Close Android Emulator View +QUESTION_AndroidView_StopAllInstancesOnDisposeMessage=Do you wish to stop the running Android emulator \ + instances as well? +UI_AbstractAndroidView_StopInstanceJob=Stop Device +QUESTION_AbstractAndroidView_OpenViewForStartedEmulatorsTitle=Open Android Emulator View +QUESTION_AbstractAndroidView_OpenViewForStartedEmulatorsMessage=Do you wish to display the Android Virtual Device(s) within an Eclipse view? +QUESTION_RunningInstancesOnClose_Title = Running Android Virtual Devices +QUESTION_RunningInstancesOnClose_Text = There are Android Virtual Devices running. Do you want to stop them? +QUESTION_NativeWindow_LooseOriginalScale_Title = Start up arguments +QUESTION_NativeWindow_LooseOriginalScale_Text = By zooming the emulator image you will lose the scale options used to start the Android Virtual Device. Do you want to continue? +WARN_RunningInstancesOnClose_Linux_Title = Running Android Virtual Devices +WARN_RunningInstancesOnClose_Linux_Text = The running Android Virtual Devices will be stopped +UI_LayoutContributionItem_NoLayoutsAvailable=No layouts +ERR_CannotConnectToVNC=Could not establish a connection to emulator "{0}" +EXC_AbstractAndroidView_ViewNotAccessibleProgramatically=The Android Emulator view could not be opened automatically. \ + Open it manually by selecting "Show View" from the "Window" menu. +ERR_StopEmulatorHandler_NotAnAndroidEmulator=The service is being run with a device instance that is not supported by the service. Aborting execution. +ERR_StartEmulatorHandler_NotAnAndroidEmulator=The service is being run with a device instance that is not supported by the service. Aborting execution. +ERR_AndroidSkinTranslator_ErrorReadingKeycodeFile=It was not possible to retrieve key-related data from the skin. It will not be possible to use the mouse in this session. +ERR_AndroidSkin_NoLayoutLoaded=No skin was loaded. Check if the skin assigned to the current AVD is available and not corrupted. +ERR_AndroidSkin_InvalidLayoutProvided=The provided layout name does not exist in the skin being used by the instance. The current operation is being aborted. +ERR_AndroidSkin_ProvidedSkinPathIsNotADirectory=The selected skin was not found. Check your AVD configuration and try again later. +ERR_LayoutFileParser_BracketsDoNotMatch=The skin layout file is corrupted. It is not possible to load this skin. It will not be possible to use the mouse in this session. +ERR_LayoutFileParser_LayoutFileCouldNotBeRead=The skin layout file could not be read. It is not possible to load this skin. It will not be possible to use the mouse in this session. +PropertiesMainComposite_ABITypeLabel=ABI Type: +PropertiesMainComposite_ProxySettings_CheckboxLabel=Use settings from Eclipse Network Settings +PropertiesMainComposite_ProxySettings_GroupTitle=Proxy Settings +PropertiesMainComposite_ProxySettings_LinkToPreference=<a>Configure network settings</a> +PropertiesMainComposite_SaveSnapshot=Save to snapshot on exit +PropertiesMainComposite_SDCard_Size_Invalid_Integer=The SD card size is not a valid integer. +PropertiesMainComposite_SnapshotSettings=Snapshot Settings +PropertiesMainComposite_startFromSnapshot=Launch emulator from snapshot +PropertiesMainComposite_UseSnapshot=Enable snapshot +RepairAvdHandler_AVD_NOT_REPAIRABLE=The selected AVD is not repairable. +RepairAvdHandler_Not_Android_Instance=Aborting repair service. This is not an Android Emulator instance... + +ERR_PropertiesMainComposite_ABINotAvailable=You must select one ABI type to create an AVD. If none are available, use the Android SDK Manager to download the ABI system image. +StartupOptionsComposite_Error_Loading_Skin_Cant_Calculate_Scale=Could not read emulator skin. Calculating scale will not be possible. diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AbstractStartAndroidEmulatorLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AbstractStartAndroidEmulatorLogic.java new file mode 100644 index 0000000..004ac7d --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AbstractStartAndroidEmulatorLogic.java @@ -0,0 +1,69 @@ +/* +* 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.info; + +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osgi.util.NLS; + +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +public abstract class AbstractStartAndroidEmulatorLogic implements IAndroidLogic +{ + + public static enum LogicMode + { + START_MODE, TRANSFER_AND_CONNECT_VNC, RESTART_VNC_SERVER, DO_NOTHING; + } + + public final void execute(IAndroidLogicInstance instance, int timeout, IProgressMonitor monitor) + throws InstanceStartException, StartCancelledException, StartTimeoutException, + IOException + { + this.execute(instance, LogicMode.START_MODE, timeout, monitor); + } + + public final void execute(IAndroidLogicInstance instance, LogicMode mode, int timeout, + IProgressMonitor monitor) throws InstanceStartException, StartCancelledException, + StartTimeoutException, IOException + + { + for (IAndroidLogic logic : getLogicCollection(instance, mode)) + { + long timeoutLimit = AndroidLogicUtils.getTimeoutLimit(timeout); + AndroidLogicUtils.testCanceled(monitor); + AndroidLogicUtils.testTimeout(timeoutLimit, NLS.bind( + EmulatorNLS.EXC_TimeoutWhileStarting, instance.getName())); + info("Executing " + logic.getClass().getSimpleName() + " for " + instance); + long startTime = System.currentTimeMillis(); + logic.execute(instance, timeout, monitor); + long endTime = System.currentTimeMillis(); + int duration = (int) (endTime - startTime); + timeout = timeout - duration; + } + } + + public abstract Collection<IAndroidLogic> getLogicCollection(IAndroidLogicInstance instance, + LogicMode mode); + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AndroidExceptionHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AndroidExceptionHandler.java new file mode 100644 index 0000000..0bb31bd --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AndroidExceptionHandler.java @@ -0,0 +1,392 @@ +/* +* 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.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate; +import org.eclipse.sequoyah.vnc.protocol.lib.IProtocolExceptionHandler; +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.InvalidDefinitionException; +import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.InvalidInputStreamDataException; +import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.InvalidMessageException; +import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.MessageHandleException; +import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.ProtocolHandshakeException; +import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.ProtocolRawHandlingException; + +import com.motorola.studio.android.adt.ISerialNumbered; +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.exception.InstanceNotFoundException; +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.utils.EmulatorCoreUtils; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic.LogicMode; + +/** + * DESCRIPTION: + * Class that defines how to handle internal protocol exceptions + * + * RESPONSABILITY: + * Handle internal protocol exceptions + * + * COLABORATORS: + * None. + * + * USAGE: + * This class shall be used by Eclipse only + */ +public class AndroidExceptionHandler implements IProtocolExceptionHandler +{ + private int handlingLevel = 1; + + private boolean checkThreadRunning = false; + + private Lock lock = new ReentrantReadWriteLock().writeLock(); + + private static Collection<String> stoppedWithFailure = new HashSet<String>(); + + static + { + + IJobManager manager = Job.getJobManager(); + manager.addJobChangeListener(new JobChangeAdapter() + { + @Override + public void done(IJobChangeEvent event) + { + Job job = event.getJob(); + if (job.belongsTo(StartVncServerLogic.VNC_SERVER_JOB_FAMILY)) + { + IStatus result = event.getResult(); + if (!result.isOK() && !(result.getSeverity() == IStatus.CANCEL)) + { + stoppedWithFailure.add(job.getName()); + } + } + } + + @Override + public void scheduled(IJobChangeEvent event) + { + Job job = event.getJob(); + if (job.belongsTo(StartVncServerLogic.VNC_SERVER_JOB_FAMILY)) + { + stoppedWithFailure.remove(job.getName()); + } + } + }); + } + + /** + * Handles internal IOExceptions caught by the protocol plugin during its execution. + * + * @see IProtocolExceptionHandler#handleIOException(ProtocolHandle, IOException) + */ + public void handleIOException(ProtocolHandle handle, IOException e) + { + error("A socket was broken while communicating to server. Cause: " + e.getMessage()); + handleException(handle); + } + + /** + * Handles exceptions thrown by the protocol plugin when it detects an invalid message + * definition provided by the plugin which is extending it (in this case, the core plugin). + * + * @see IProtocolExceptionHandler#handleInvalidDefinitionException(ProtocolHandle, InvalidDefinitionException) + */ + public void handleInvalidDefinitionException(ProtocolHandle handle, InvalidDefinitionException e) + { + // This exception should not happen, because the message definitions are provided + // by the development team. + warn("An invalid message definition was detected. Cause: " + e.getMessage()); + handleException(handle); + } + + /** + * Handles exceptions thrown by the protocol plugin when it detects that the data retrieved from + * the connection does not match the format defined in the message definition. + * + * @see IProtocolExceptionHandler#handleInvalidInputStreamDataException(ProtocolHandle, InvalidInputStreamDataException) + */ + public void handleInvalidInputStreamDataException(ProtocolHandle handle, + InvalidInputStreamDataException e) + { + // If the data retrieved from the connection is not as expected (considering + // the message definition provided), there is a high chance of errors to happen. + // It is likely that the data from stream is no longer synchronized, so the + // exception handling for this case is to restart connection. + + error("Some received data is not compatible with the expected definition. Restarting the protocol for synchronization."); + handleException(handle); + } + + /** + * Handles exceptions thrown by the protocol plugin when it detects that the message + * provided for sending does not have enough or valid information, given a corresponding + * message definition + * + * @see IProtocolExceptionHandler#handleInvalidMessageException(ProtocolHandle, InvalidMessageException) + */ + public void handleInvalidMessageException(ProtocolHandle handle, InvalidMessageException e) + { + // This exception should not happen, because the message object data is provided + // by the development team. Log only. + warn("A message was not constructed according to its definition. Cause: " + e.getMessage()); + handleException(handle); + } + + /** + * Handles exceptions thrown by any message handler when they discovers that it + * is not possible to handle the message and it is a fatal error for the protocol. + * + * @see IProtocolExceptionHandler#handleMessageHandleException(ProtocolHandle, MessageHandleException) + */ + public void handleMessageHandleException(ProtocolHandle handle, MessageHandleException e) + { + // If a message handler throws a MessageHandleException, that means that it cannot + // continue. Restart the protocol to guarantee the synchronization. + error("A message handler has ended in error and has thrown an exception meaning the protocol cannot continue."); + handleException(handle); + } + + /** + * Handles exceptions thrown by the protocol plugin when it is not possible to + * init the protocol, for example because the handshaking procedure has failed. + * + * @see IProtocolExceptionHandler#handleProtocolHandshakeException(ProtocolHandle, ProtocolHandshakeException) + */ + public void handleProtocolHandshakeException(ProtocolHandle handle, ProtocolHandshakeException e) + { + error("Could not initialize the protocol."); + handleException(handle); + } + + /** + * Handles exceptions thrown by any raw field handler when they discovers that it + * is not possible to handle the field and it is a fatal error for the protocol. + * + * @see IProtocolExceptionHandler#handleProtocolRawHandlingException(ProtocolHandle, ProtocolRawHandlingException) + */ + public void handleProtocolRawHandlingException(ProtocolHandle handle, + ProtocolRawHandlingException e) + { + // This message should be thrown by raw field handlers when they cannot handle the + // raw field and need to abort the protocol execution. Restart the protocol to + // guarantee the synchronization. + error("A raw field handler has ended in error and has thrown an exception meaning the protocol cannot continue."); + handleException(handle); + } + + /** + * This method will be called whenever an exception happens. It is important to find + * out if the failure happened during an start or restart procedure, so that we can + * do appropriate handling to each situation. + * + * @param handle The object that identifies the protocol instance + */ + private void handleException(ProtocolHandle handle) + { + IAndroidEmulatorInstance instance = null; + if (lock.tryLock()) + { + try + { + instance = EmulatorCoreUtils.getAndroidInstanceByHandle(handle); + + try + { + debug("Check if device is online: " + instance); + if (instance instanceof ISerialNumbered) + { + String serialNumber = ((ISerialNumbered) instance).getSerialNumber(); + AndroidLogicUtils.testDeviceStatus(serialNumber); + } + } + catch (Exception e) + { + error("Device is not online. Abort VNC session..."); + abort(instance); + } + + if ((handlingLevel == 3) && canRestartServer(instance)) + { + handlingLevel--; + } + + // Firstly, try to restart only the VNC client. If restarting the VNC client + // is not possible, try to restart the VNC server at the device and to connect + // to it again. If the start logic cannot be retrieved, delegate the decision + // to the user. + + if (handlingLevel == 1) + { + restartClientOnly(handle); + handlingLevel++; + } + else if (handlingLevel == 2) + { + restartServerAndClient(instance, handle); + handlingLevel++; + } + else + { + if (delegateDecisionToUser(handle)) + { + abort(instance); + } + } + } + catch (InstanceNotFoundException e) + { + // If the instance is not found, it means that the instance is stopped. + // In this case, a restart is not applicable. + } + finally + { + lock.unlock(); + } + } + } + + private boolean canRestartServer(IAndroidEmulatorInstance instance) + { + String name = StartVncServerLogic.VNC_SERVER_JOB_PREFIX + instance.getName(); + return !stoppedWithFailure.contains(name); + } + + private void restartClientOnly(final ProtocolHandle handle) + { + PluginProtocolActionDelegate.requestRestartProtocol(handle); + + if (!checkThreadRunning) + { + Runnable r = new Runnable() + { + public void run() + { + checkThreadRunning = true; + while (checkThreadRunning) + { + if (PluginProtocolActionDelegate.isProtocolRunning(handle)) + { + handlingLevel = 1; + checkThreadRunning = false; + } + + try + { + Thread.sleep(500); + } + catch (InterruptedException e) + { + // Do nothing. + } + } + } + }; + (new Thread(r)).start(); + } + } + + private void restartServerAndClient(IAndroidEmulatorInstance instance, ProtocolHandle handle) + { + try + { + if (instance instanceof IAndroidLogicInstance) + { + IAndroidLogicInstance logicInstance = (IAndroidLogicInstance) instance; + AbstractStartAndroidEmulatorLogic logic = logicInstance.getStartLogic(); + logic.execute(logicInstance, LogicMode.TRANSFER_AND_CONNECT_VNC, logicInstance + .getTimeout(), new NullProgressMonitor()); + try + { + Thread.sleep(1500); + } + catch (InterruptedException e) + { + // Do nothing. + } + PluginProtocolActionDelegate.requestRestartProtocol(handle); + } + else + { + handlingLevel = 3; + } + } + catch (Exception e1) + { + handlingLevel = 3; + } + } + + /** + * In this method, the user is asked whether to retry or not. While the user does not + * give up, the instance retries to connect to the emulator. When the user gives up, + * the instance is stopped. + * + * @param handle The object that identifies the protocol instance + */ + private boolean delegateDecisionToUser(ProtocolHandle handle) + { + boolean abort = true; + error("Cannot reconnect to VM. Asking to the user if he/she wants to retry."); + if (EclipseUtils.showQuestionDialog(EmulatorNLS.GEN_Question, + EmulatorNLS.QUESTION_AndroidExceptionHandler_ImpossibleToReconnect)) + { + info("User chose to retry to reconnect to emulator VNC server."); + PluginProtocolActionDelegate.requestRestartProtocol(handle); + handlingLevel = 2; + abort = false; + } + return abort; + } + + /** + * + */ + private void abort(IAndroidEmulatorInstance instance) + { + info("User chose to stop the instance."); + try + { + checkThreadRunning = false; + instance.stop(true); + } + catch (InstanceStopException e1) + { + error("Error while running service for stopping virtual machine"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_AndroidExceptionHandler_CannotRunStopService); + } + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AndroidLogicUtils.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AndroidLogicUtils.java new file mode 100644 index 0000000..aad3b07 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/AndroidLogicUtils.java @@ -0,0 +1,360 @@ +/* +* 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.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osgi.util.NLS; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.adt.ISerialNumbered; +import com.motorola.studio.android.common.exception.AndroidException; +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * This class is used as an utilities class for operations related to Android Devices + * + */ +public class AndroidLogicUtils +{ + private static final int INITIAL_VNC_PORT_VALUE = 5900; + + public static final String ORIENTATION_BASE_COMMAND = "sendevent /dev/input/event0 "; + + /** + * Execute the VM process + * + * @param cmd the command to be executed + * @return the VM process + * + * @throws AndroidException if the command failed to execute + */ + public static Process executeProcess(final String cmd) throws AndroidException + { + try + { + info("Executing command " + cmd); + Process vmProcess = Runtime.getRuntime().exec(cmd); + return vmProcess; + } + catch (IOException e) + { + error("Falied to execute the command: " + cmd); + throw new AndroidException(NLS.bind( + EmulatorNLS.EXC_AndroidLogicUtils_CannotStartProcess, cmd)); + } + } + + /** + * Execute the VM process + * + * @param cmd the command to be executed, described as an array + * @return the VM process + * + * @throws AndroidException if the command failed to execute + */ + public static Process executeProcess(final String[] cmd) throws AndroidException + { + String cmdString = ""; + for (int i = 0; i < cmd.length; i++) + { + cmdString += cmd[i] + " "; + + } + + return executeProcess(cmd, cmdString); + } + + /** + * Execute the VM process + * + * @param cmd The command to be executed, described as an array + * @param cmdToLog The command to be logged. + * + * @return the VM process + * + * @throws AndroidException if the command failed to execute + */ + public static Process executeProcess(final String[] cmd, final String cmdToLog) + throws AndroidException + { + try + { + info("Executing command " + cmdToLog); + Process vmProcess = Runtime.getRuntime().exec(cmd); + return vmProcess; + } + catch (IOException e) + { + error("Falied to execute the command: " + cmd); + throw new AndroidException(NLS.bind( + EmulatorNLS.EXC_AndroidLogicUtils_CannotStartProcess, cmd)); + } + } + + /** + } + + /** + * Checks if the user has canceled the VM startup + * + * @param monitor A progress monitor that will give the user feedback about this + * long running operation + * @param instanceHost The IP address of the started emulator instance + * + * @return True if the operation can proceed, false otherwise + * + * @throws StartCancelledException If the user has canceled the start process + */ + public static void testCanceled(IProgressMonitor monitor) throws StartCancelledException + { + if (monitor.isCanceled()) + { + info("Operation canceled by the user"); + monitor.subTask(EmulatorNLS.MON_AndroidEmulatorStarter_Canceling); + + throw new StartCancelledException( + EmulatorNLS.EXC_AndroidEmulatorStarter_EmulatorStartCanceled); + } + } + + /** + * Checks if the timeout limit has reached + * + * @param timeoutLimit The system time limit that cannot be overtaken, in milliseconds + * + * @throws StartTimeoutException When the system time limit is overtaken + */ + public static void testTimeout(long timeoutLimit, String timeoutErrorMessage) + throws StartTimeoutException + { + if (System.currentTimeMillis() > timeoutLimit) + { + error("The emulator was not up within the set timeout"); + throw new StartTimeoutException(timeoutErrorMessage); + } + } + + /** + * Get the relative timeout limit, which is the the current time plus the timeout value + * + * @param timeout timeout value (in milliseconds) + * @return Relative timeout limit + */ + public static long getTimeoutLimit(int timeout) + { + return System.currentTimeMillis() + timeout; + } + + /** + * Check if the given process is still up and running + * + * @param p process + * @throws InstanceStartException + */ + public static void testProcessStatus(Process p) throws InstanceStartException + { + + boolean isRunning; + int exitCode; + + try + { + exitCode = p.exitValue(); + isRunning = false; + } + catch (Exception e) + { + // emulator process is still running... so everything looks fine... + isRunning = true; + exitCode = 0; + } + + if (!isRunning) + { + error("Emulator process is not running! Exit code:" + exitCode); + StringBuffer outBuf = null; + InputStream inStream = null; + + int ch; + + //Getting error output stream + String processAnswer = ""; + inStream = p.getErrorStream(); + outBuf = new StringBuffer(); + try + { + while ((ch = inStream.read()) != -1) + { + outBuf.append((char) ch + ""); + } + } + catch (IOException e) + { + error("Cannot read error output stream from Emulator proccess"); + } + + processAnswer = outBuf.toString(); + + if (processAnswer.length() == 0) + { + //if no error came from process, get standard output stream + inStream = p.getInputStream(); + outBuf = new StringBuffer(); + try + { + while ((ch = inStream.read()) != -1) + { + outBuf.append((char) ch + ""); + } + } + catch (IOException e) + { + error("Cannot read standard output stream from Emulator proccess"); + } + + processAnswer = outBuf.toString(); + + } + String msg = EmulatorNLS.EXC_AndroidEmulatorStarter_ProcessTerminated; + msg += processAnswer; + throw new InstanceStartException(msg); + } + + } + + /** + * Kill the communication channel + * + * @param instance Android instance + */ + public static void kill(IAndroidLogicInstance instance) + { + if (instance instanceof ISerialNumbered) + { + String serialNumber = ((ISerialNumbered) instance).getSerialNumber(); + DDMSFacade.kill(serialNumber); + Process process = instance.getProcess(); + if (process != null) + { + int tries = 0; + Integer exitValue = null; + while ((process != null) && (tries < 10) && (exitValue == null)) + { + try + { + exitValue = process.exitValue(); + } + catch (Throwable t) + { + tries++; + try + { + Thread.sleep(250); + } + catch (InterruptedException e) + { + //do nothing + } + } + } + process.destroy(); + instance.setProcess(null); + } + } + } + + /** + * Get the VNC port forward + * + * @param serial port number + * @return VNC port + */ + public static int getVncServerPortFoward(String serial) + { + if (serial == null) + { + return 0; + } + + int stringSize = serial.length(); + String lastTwoNumbers = serial.substring(stringSize - 2, stringSize); + + int port = INITIAL_VNC_PORT_VALUE; + + try + { + port += Integer.valueOf(lastTwoNumbers); + } + catch (NumberFormatException e) + { + // do nothing (this should not happen) + } + + return port; + + } + + public static int getEmulatorPort(String serial) + { + if (serial == null) + { + return 0; + } + + int stringSize = serial.length(); + String lastFourNumbers = serial.substring(stringSize - 4, stringSize); + + int port = 0; + + try + { + port = Integer.valueOf(lastFourNumbers); + } + catch (NumberFormatException e) + { + // do nothing (this should not happen) + } + return port; + } + + /** + * Checks if the Device is still online... + * If the device is not online it is not possible to communicate with it. + * Notice it is a verification of the status of the Device wich may be different than the status of the Tml Instance... + * + * @param serialNumber serial number of the device + * + * @throws AndroidException If the device is not started + */ + public static void testDeviceStatus(String serialNumber) throws AndroidException + { + if (!DDMSFacade.isDeviceOnline(serialNumber)) + { + info("Device is offline: " + serialNumber); + + throw new AndroidException(EmulatorNLS.EXC_AndroidLogicUtils_DeviceIsOffline); + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/ConnectVncLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/ConnectVncLogic.java new file mode 100644 index 0000000..37014db --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/ConnectVncLogic.java @@ -0,0 +1,227 @@ +/* +* 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.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.osgi.util.NLS; +import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate; +import org.eclipse.sequoyah.vnc.protocol.lib.IProtocolExceptionHandler; +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; + +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * This class contains the logic to stablish VNC connections + * + */ +public class ConnectVncLogic implements IAndroidLogic +{ + /** + * The port that is used to start the communication with the instance. + * It corresponds to the VNC display 1 port + */ + private static final String LOCALHOST_IP_ADDRESS = "127.0.0.1"; + + public IJobChangeEvent vncServerDoneEvent = null; + + /** + * Initialize by connecting to VNC + * + * @see com.motorola.studio.android.emulator.logic.IAndroidLogic#execute(IAndroidLogicInstance, int, IProgressMonitor) + */ + public void execute(final IAndroidLogicInstance instance, final int timeout, + IProgressMonitor monitor) throws StartTimeoutException, StartCancelledException, + InstanceStartException + { + connectVnc(instance, timeout, monitor); + } + + /** + * Connect to VNC + * + * @param instance instance to connect + * @param timeout timeout for the operation + * @param monitor monitor for this operation + * + * @throws InstanceStartException + */ + private void connectVnc(IAndroidLogicInstance instance, int timeout, IProgressMonitor monitor) + throws StartTimeoutException, StartCancelledException, InstanceStartException + { + info("Trying to estabilish vnc connection with " + instance.getName()); + long timeoutLimit = System.currentTimeMillis() + timeout; + try + { + startProtocol(instance, timeoutLimit, AndroidLogicUtils.getVncServerPortFoward(instance + .getInstanceIdentifier()), monitor); + } + catch (StartTimeoutException ise) + { + info("The protocol or the emulator services could not be launched. Stopping the instance."); + throw ise; + } + info("VNC Protocol is running for " + instance.getName()); + } + + /** + * Starts protocol connection + * + * @param instance The Android device instance + * @param timeoutLimit The timestamp of the time when timeout happens + * @param instanceHost The IP address of the started emulator instance + * @param monitor A progress monitor that will give the user feedback about this + * long running operation + * + * @throws InstanceStartException If some fatal error occurs during the start process, + * that may require status update at the clients + * @throws StartCancelledException If the user presses the "Cancel" button at the progress monitor + * @throws InstanceStartException + */ + private void startProtocol(IAndroidEmulatorInstance instance, long timeoutLimit, int port, + IProgressMonitor monitor) throws StartTimeoutException, StartCancelledException, + InstanceStartException + { + try + { + monitor.beginTask(EmulatorNLS.MON_AndroidEmulatorStarter_ConnectingToEmulator, 100); + monitor.setTaskName(EmulatorNLS.MON_AndroidEmulatorStarter_ConnectingToEmulator); + + testVncServer(instance); + AndroidLogicUtils.testCanceled(monitor); + requestStartProtocol(instance, port); + ProtocolHandle handle = instance.getProtocolHandle(); + + while (!PluginProtocolActionDelegate.isProtocolRunning(handle)) + { + AndroidLogicUtils.testCanceled(monitor); + + try + { + Thread.sleep(500); + } + catch (InterruptedException e1) + { + // Do nothing. + } + + AndroidLogicUtils.testTimeout(timeoutLimit, + EmulatorNLS.EXC_AndroidEmulatorStarter_TimeoutWhileRunningProtocol); + + } + monitor.worked(100); + } + finally + { + monitor.done(); + } + } + + /** + * Set if the job has been completed + * + * @param jobEvent event job + */ + public void setVncServerDoneEvent(IJobChangeEvent jobEvent) + { + this.vncServerDoneEvent = jobEvent; + } + + /** + * Test if the VNC server is up and running + * + * @param instance emulator instance + * + * @throws InstanceStartException + */ + private void testVncServer(final IAndroidEmulatorInstance instance) + throws InstanceStartException + { + if (vncServerDoneEvent != null) + { + IStatus jobResult = vncServerDoneEvent.getResult(); + String reason = ""; + if (IStatus.ERROR == jobResult.getSeverity()) + { + reason = jobResult.getMessage(); + } + else if (Status.CANCEL_STATUS.equals(jobResult)) + { + reason = EmulatorNLS.INFO_ConnectVncLogic_UserCancelledVncServerStart; + } + + String message = + NLS.bind(EmulatorNLS.EXC_VncServerNotRunning, new String[] + { + instance.getName(), reason + }); + throw new InstanceStartException(message); + } + } + + /** + * Starts the protocol execution, connecting to the server accessible through + * the provided Android Emulator instance + * + * @param androidInstance The Android device instance + */ + private void requestStartProtocol(IAndroidEmulatorInstance androidInstance, int port) + throws InstanceStartException + { + if (androidInstance != null) + { + ProtocolHandle handle = null; + + try + { + // Start protocol and screen update + info("Requesting protocol start"); + Map<String, Object> parameters = new HashMap<String, Object>(); + parameters.put("password", ""); + parameters.put("bypassProxy", new Boolean(true)); + IProtocolExceptionHandler excHandler = new AndroidExceptionHandler(); + handle = + PluginProtocolActionDelegate.requestStartProtocolAsClient("vncProtocol38", + excHandler, LOCALHOST_IP_ADDRESS, port, parameters); + + androidInstance.setProtocolHandle(handle); + } + catch (Exception e) + { + error("There is an error at the protocol specification."); + throw new InstanceStartException(EmulatorNLS.EXC_CouldNotStartProtocol); + } + } + else + { + error("Could not start the protocol, because the provided instance is null"); + throw new InstanceStartException(EmulatorNLS.EXC_CouldNotStartProtocol); + } + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/ForwardVncPortLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/ForwardVncPortLogic.java new file mode 100644 index 0000000..2884ff3 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/ForwardVncPortLogic.java @@ -0,0 +1,55 @@ +/* +* 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 java.io.IOException; + +import org.eclipse.core.runtime.IProgressMonitor; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.adt.ISerialNumbered; +import com.motorola.studio.android.common.exception.AndroidException; +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; + +public class ForwardVncPortLogic implements IAndroidLogic +{ + + public void execute(IAndroidLogicInstance instance, int timeout, IProgressMonitor monitor) + throws IOException, InstanceStartException + { + int port = AndroidLogicUtils.getVncServerPortFoward(instance.getInstanceIdentifier()); + + if (instance instanceof ISerialNumbered) + { + String serialNumber = ((ISerialNumbered) instance).getSerialNumber(); + try + { + AndroidLogicUtils.testDeviceStatus(serialNumber); + } + catch (AndroidException e) + { + throw new InstanceStartException(e.getMessage()); + } + + boolean forwardOk = DDMSFacade.createForward(serialNumber, port, 5901); + if (!forwardOk) + { + throw new IOException("Could not forward VNC port 5901 to " + port + " on " + + instance.getName()); + } + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/IAndroidLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/IAndroidLogic.java new file mode 100644 index 0000000..d3028b2 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/IAndroidLogic.java @@ -0,0 +1,30 @@ +/* +* 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 java.io.IOException; + +import org.eclipse.core.runtime.IProgressMonitor; + +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; + +public interface IAndroidLogic +{ + void execute(IAndroidLogicInstance instance, int timeout, IProgressMonitor monitor) + throws InstanceStartException, StartTimeoutException, StartCancelledException, IOException; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/IAndroidLogicInstance.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/IAndroidLogicInstance.java new file mode 100644 index 0000000..5f67716 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/IAndroidLogicInstance.java @@ -0,0 +1,103 @@ +/* +* 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 java.io.File; +import java.util.List; +import java.util.Properties; + +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; + +/** + * DESCRIPTION: + * This class adds to the Android Emulator instance contract + * some specific methods related to services logic. + * + * RESPONSIBILITY: + * Define which additional information is required from a services to + * plug to the Android Emulator viewer. + * + * COLABORATORS: + * None. + * + * USAGE: + * Use the methods to retrieve information from a Android Emulator device instance + */ +public interface IAndroidLogicInstance extends IAndroidEmulatorInstance +{ + /** + * Get the command lines arguments for the instance + * + * @return list of command line arguments + */ + String getCommandLineArguments(); + + /** + * Get the command lines arguments for the instance, returned as a Property object + * + * @return list of command line arguments + */ + Properties getCommandLineArgumentsAsProperties(); + + /** + * Get the Start logic for this instance, which is the commands used to start the instance + * + * @return Start logic class + */ + AbstractStartAndroidEmulatorLogic getStartLogic(); + + /** + * Check if there is a device connected to this instance + * + * @return true if there is a device connected, false otherwise + */ + boolean hasDevice(); + + /** + * Get the reference to the File that point to the filesystem location where the + * user data of the VM is. + * + * @return the File object that references the filesystem location where the userdata + * of the given VM should be. Returns a null reference if SDK is not configured + * or if there is no VM with the given name. + */ + File getUserdata(); + + /** + * Get the reference to the files in the VM folder that contain state data. + * + * @return File objects that reference files in the VM folder that contain state data. + */ + List<File> getStateData(); + + /** + * Tests if there is a userdata file for this android device instance or if it is clean, + * i.e, there is no user data file for this Android Device Instance. + * + * @return True if there is no working copy at that location; false otherwise + */ + boolean isClean(); + + /** + * Get the timeout value to start the instance + * + * @return timeout value + */ + int getTimeout(); + + File getSnapshotOriginalFilePath(); + +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/StartEmulatorProcessLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/StartEmulatorProcessLogic.java new file mode 100644 index 0000000..e71eba0 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/StartEmulatorProcessLogic.java @@ -0,0 +1,523 @@ +/* +* 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); + } +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/StartVncServerLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/StartVncServerLogic.java new file mode 100644 index 0000000..b28cc9a --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/StartVncServerLogic.java @@ -0,0 +1,229 @@ +/* +* 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.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeListener; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.adt.ISerialNumbered; +import com.motorola.studio.android.common.exception.AndroidException; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; + +/** + * This class contains the logic to start the VNC server on the given Emulator. + */ +public final class StartVncServerLogic implements IAndroidLogic +{ + public static final String VNC_SERVER_JOB_PREFIX = "VNC Server - "; + + public static final Object VNC_SERVER_JOB_FAMILY = new Object(); + + /** + * Sequence of commands that must be executed on the emulator to start the VNC server + */ + private final Collection<String> remoteCommands = new LinkedList<String>(); + + /** + * Collection of listeners for the job executing the VNC server. + */ + private final Collection<IJobChangeListener> listeners = new LinkedList<IJobChangeListener>(); + + /** + * Executes the logic to start the vnc server. + */ + public void execute(final IAndroidLogicInstance instance, int timeout, + final IProgressMonitor monitor) throws InstanceStartException, StartTimeoutException, + StartCancelledException, IOException + { + cancelCurrentVncServerJobs(instance); + + // Creates and starts a job that will keep running as long as the VNC server is up on that Emulator instance. + // add listeners that will receive notifications about the Job life-cycle. + VncServerJob vncServerJob = new VncServerJob(instance, getRemoteCommands()); + for (IJobChangeListener vncServerListener : listeners) + { + vncServerJob.addJobChangeListener(vncServerListener); + } + vncServerJob.schedule(); + + } + + /** + * Cancel any VncServerJob that is currently running the VNC server on the given emulator instance. + * @param instance, the emulator instances where VNC server execution must be canceled. + **/ + public static void cancelCurrentVncServerJobs(IAndroidEmulatorInstance instance) + { + // stop the previous VNC Server job for this instance if any... + IJobManager manager = Job.getJobManager(); + Job[] allVncJobs = manager.find(StartVncServerLogic.VNC_SERVER_JOB_FAMILY); + if (allVncJobs.length > 0) + { + for (Job job : allVncJobs) + { + if (job.getName().equals( + StartVncServerLogic.VNC_SERVER_JOB_PREFIX + instance.getName())) + { + info("Cancel execution of the VNC Server on " + instance); + job.cancel(); + } + } + } + } + + /** + * Add job listener to receive state-change notifications from the job that runs the VNC Server. + * @param vncServerListener job listener that willl receive state change notifications from the VNC Serever job. + */ + public void addVncServerJobListener(IJobChangeListener vncServerListener) + { + listeners.add(vncServerListener); + } + + /** + * Add a command to be executed in the process of starting the VNC Server on the Emulator. + * @param remoteCommand + */ + public void addRemoteCommand(String remoteCommand) + { + remoteCommands.add(remoteCommand); + } + + /** + * Get the list of commands to be executed on the Emulator in order to start the VNC Server. + * @return the sequence of commands that must be executed on the Emulator to start the VNC Server. + */ + public Collection<String> getRemoteCommands() + { + return remoteCommands; + } + +} + +/** + * Job that executes the VNC Server. + * It will keep running as long as the VNC Server process is running on the Emulator. + */ +class VncServerJob extends Job implements ISchedulingRule +{ + private String serialNumber; + + /** + * Sequence of commands that must be executed on the emulator to start the VNC server + */ + private final Collection<String> remoteCommands; + + /** + * Creates a new job to execute the VNC server on the given emulator instance. + * @param instance, emulator instance where the VNC server will be started. + * @param remoteCommands, sequence of commands that must be executed on the given emulator instance to start the VNC Server. + * @throws InstanceStartException + */ + public VncServerJob(IAndroidLogicInstance instance, Collection<String> remoteCommands) + throws InstanceStartException + { + super(StartVncServerLogic.VNC_SERVER_JOB_PREFIX + instance.getName()); + + this.serialNumber = ((ISerialNumbered) instance).getSerialNumber(); + + try + { + AndroidLogicUtils.testDeviceStatus(serialNumber); + } + catch (AndroidException e) + { + throw new InstanceStartException(e.getMessage()); + } + + this.remoteCommands = remoteCommands; + setSystem(true); + setRule(this); + } + + /** + * @see org.eclipse.core.runtime.jobs.Job#run(IProgressMonitor) + */ + @Override + public IStatus run(IProgressMonitor monitor) + { + IStatus status = Status.OK_STATUS; + try + { + info("Executing VNC Server on " + serialNumber); + AndroidLogicUtils.testDeviceStatus(serialNumber); + DDMSFacade.execRemoteApp(serialNumber, remoteCommands, monitor); + + if (monitor.isCanceled()) + { + status = Status.CANCEL_STATUS; + } + } + catch (Exception e) + { + String errorMessage = "Error while trying to run the VNC server on " + serialNumber; + error(errorMessage + " " + e.getMessage()); + status = new Status(IStatus.CANCEL, EmulatorPlugin.PLUGIN_ID, errorMessage, e); + } + + info("Finished the execution of the VNC Server on " + serialNumber + " with status " + + status); + + return status; + } + + /** + * @see org.eclipse.core.runtime.jobs.Job#belongsTo(Object) + */ + @Override + public boolean belongsTo(Object family) + { + return StartVncServerLogic.VNC_SERVER_JOB_FAMILY.equals(family); + } + + public boolean contains(ISchedulingRule rule) + { + boolean contains = false; + if (rule instanceof VncServerJob) + { + VncServerJob otherVncServerJob = (VncServerJob) rule; + contains = otherVncServerJob.serialNumber.equals(serialNumber); + } + + return contains; + } + + public boolean isConflicting(ISchedulingRule rule) + { + return contains(rule); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/TransferFilesLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/TransferFilesLogic.java new file mode 100644 index 0000000..9df8eb6 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/TransferFilesLogic.java @@ -0,0 +1,97 @@ +/* +* 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.error; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.emulator.core.exception.InstanceStartException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.exception.StartTimeoutException; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +public class TransferFilesLogic implements IAndroidLogic +{ + private String localDir = ""; + + private String remoteDir = ""; + + private final Collection<String> filenames = new LinkedList<String>(); + + public void execute(IAndroidLogicInstance instance, int timeout, IProgressMonitor monitor) + throws InstanceStartException, StartCancelledException, StartTimeoutException, + IOException + { + if ((instance != null) && (timeout > 0) && (localDir != null) && (!"".equals(localDir)) + && (remoteDir != null) && ("".equals(remoteDir))) + { + error("Cannot transfer files because the parameters provided are not as expected."); + throw new InstanceStartException( + EmulatorNLS.ERR_TransferFilesLogic_NotEnoughInformation); + } + + String serialNumber = DDMSFacade.getSerialNumberByName(instance.getName()); + IStatus status = + DDMSFacade.pushFiles(serialNumber, getLocalDir(), getFilenames(), getRemoteDir(), + timeout, monitor, null); + if (status.getSeverity() == IStatus.CANCEL) + { + throw new StartCancelledException(); + } + else if (status.getSeverity() == IStatus.ERROR) + { + throw new InstanceStartException(status.getMessage()); + } + } + + public void setLocalDir(String localDir) + { + this.localDir = localDir; + } + + public String getLocalDir() + { + return localDir; + } + + public void addFilename(String filename) + { + filenames.add(filename); + } + + public Collection<String> getFilenames() + { + return filenames; + } + + public void setRemoteDir(String remoteDir) + { + this.remoteDir = remoteDir; + } + + public String getRemoteDir() + { + return remoteDir; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/reset/AndroidEmulatorReseter.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/reset/AndroidEmulatorReseter.java new file mode 100644 index 0000000..4f0ad44 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/reset/AndroidEmulatorReseter.java @@ -0,0 +1,189 @@ +/* +* 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.reset; + +import static com.motorola.studio.android.common.log.StudioLogger.error; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.osgi.util.NLS; + +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; + +/** + * DESCRIPTION: + * This class contains the business layer of the Android + * Emulator reset procedure + * + * RESPONSIBILITY: + * Reset any Android Emulator + * + * COLABORATORS: + * None. + * + * USAGE: + * Use the public method to reset a Android Emulator + */ +public class AndroidEmulatorReseter +{ + /** + * Resets a Android Emulator based at the provided folder + * + * @param userDataFolder The folder where a working copy of the emulator is located + * @param force Perform the reset without questioning the user + * + * @return IStatus the status of the operation + */ + + static String SNAPSHOT_FILE_NAME = "snapshots.img"; + + public static IStatus resetInstance(IAndroidLogicInstance instance) + { + IStatus resetStatus = Status.OK_STATUS; + + File userData = instance.getUserdata(); + List<File> stateData = instance.getStateData(); + + if ((userData != null) || (stateData != null)) + { + List<File> allFiles = new ArrayList<File>(); + if (stateData != null) + { + allFiles.addAll(stateData); + } + if (userData != null) + { + allFiles.add(userData); + } + + for (File file : allFiles) + { + if (file.exists()) + { + if (!file.delete()) + { + error("There was an error when trying to remove the emulator instance user data files"); + resetStatus = + new Status( + IStatus.ERROR, + EmulatorPlugin.PLUGIN_ID, + NLS.bind( + EmulatorNLS.EXC_AndroidEmulatorReseter_ErrorWhilePerformingDeleteOperation, + new Path(file.getPath()).removeLastSegments(1) + .toString())); + break; + } + } + } + + // When the snapshots file is missing or corrupted after the reset, the snapshot operation will not work properly + // (when start and then closing the AVD after a reset operation, + /// the snapshots file will not be saved), that is why the error message should be shown. + + if ((allFiles != null) && (allFiles.size() > 0)) + { + File snapshot = instance.getSnapshotOriginalFilePath(); + + String snapshotToPath = + allFiles.get(0).getParentFile() + File.separator + SNAPSHOT_FILE_NAME; + + File snapshotToFile = new File(snapshotToPath); + + if ((snapshot != null) && (snapshotToFile.exists())) + { + + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + + try + { + bis = new BufferedInputStream((new FileInputStream(snapshot))); + bos = new BufferedOutputStream(new FileOutputStream(snapshotToFile)); + + int c; + while ((c = bis.read()) >= 0) + { + bos.write(c); + } + } + catch (Exception e) + { + error("Error while copying the original snapshot file to the avd that is being reseted:" + + e.getMessage()); + if (resetStatus.equals(Status.OK_STATUS)) + { + resetStatus = + new Status( + IStatus.ERROR, + EmulatorPlugin.PLUGIN_ID, + NLS.bind( + EmulatorNLS.EXC_AndroidEmulatorReseter_ErrorWhilePerformingSnapshotCopyOperation, + snapshot.getPath(), allFiles.get(0) + .getParentFile())); + } + else + { + + resetStatus = + new Status( + IStatus.ERROR, + EmulatorPlugin.PLUGIN_ID, + NLS.bind( + EmulatorNLS.EXC_AndroidEmulatorReseter_ErrorWhilePerformingDeleteSnapshotOperation, + allFiles.get(0).getParentFile(), + snapshot.getPath())); + } + } + finally + { + try + { + if (bis != null) + { + bis.close(); + } + + if (bos != null) + { + bos.close(); + } + } + catch (Exception e) + { + error("Error while closing the snapshots file of the avd that is being reseted:" + + e.getMessage()); + } + } + + } + } + + } + + return resetStatus; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/start/AndroidEmulatorStarter.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/start/AndroidEmulatorStarter.java new file mode 100644 index 0000000..cc46a21 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/start/AndroidEmulatorStarter.java @@ -0,0 +1,173 @@ +/* +* 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.start; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.Map; + +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.osgi.util.NLS; + +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.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.AbstractStartAndroidEmulatorLogic.LogicMode; + +/** + * DESCRIPTION: + * This class contains the business layer of the Android + * Emulator start procedure + * + * RESPONSIBILITY: + * Start any Android Emulator + * + * COLABORATORS: + * None. + * + * USAGE: + * Use the public method to start a Android Emulator + */ +public class AndroidEmulatorStarter +{ + + /** + * Starts this instance, after creating a clean VM copy at the provided location. + * Besides that, adds a new Android Emulator viewer inside the Android view. + * This method provides automatic VM startup. + * + * @param instance The Android device instance + * @param monitor A progress monitor that will give the user feedback about this + * long running operation + * + * @return the status of the operation (OK, Cancel or Error+ErrorMessage) + */ + public static IStatus startInstance(IAndroidLogicInstance instance, + Map<Object, Object> arguments, IProgressMonitor monitor) + { + if (instance == null) + { + error("Abort start operation. Instance is null."); + return new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, + EmulatorNLS.ERR_AndroidEmulatorStarter_InstanceNullPointer); + } + + if (monitor == null) + { + monitor = new NullProgressMonitor(); + } + + IStatus status = Status.OK_STATUS; + + if (!instance.isStarted()) + { + int timeout = instance.getTimeout(); + if (timeout <= 0) + { + status = + new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, NLS.bind( + EmulatorNLS.ERR_AndroidLogicPlugin_InvalidTimeoutValue, + new Object[] + { + timeout + })); + return status; + } + + try + { + try + { + LogicMode mode = LogicMode.START_MODE; + if (arguments != null) + { + Object modeObject = arguments.get(LogicMode.class); + if (modeObject instanceof LogicMode) + { + mode = (LogicMode) modeObject; + } + } + + AbstractStartAndroidEmulatorLogic logic = instance.getStartLogic(); + if (logic != null) + { + debug("Retrieved start logic: " + logic); + logic.execute(instance, mode, timeout, monitor); + AndroidLogicUtils.testCanceled(monitor); + } + else + { + error("Cannot start emulator because a logic is not provided for that."); + throw new InstanceStartException( + EmulatorNLS.ERR_AndroidEmulatorStarter_NoLogicAvailableForStart); + } + } + catch (Exception e) + { + error("An exception happened while trying to execute the start emulator logic for " + + instance + " Try to rollback by executing the stop process..."); + + try + { + instance.stop(true); + } + catch (InstanceStopException e1) + { + error("There was an error while forcing the stop the instance"); + } + + throw e; + } + } + catch (StartTimeoutException e) + { + error("A timeout has happeded during the start Android Emulator Instance. " + + instance + "Cause: " + e.getMessage()); + status = new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, e.getMessage(), e); + } + catch (InstanceStartException e) + { + error("It was not possible to start the Android Emulator Instance. " + instance + + "Cause: " + e.getMessage()); + status = new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, e.getMessage()); + } + catch (StartCancelledException e) + { + info("Start operation was cancelled." + instance); + status = Status.CANCEL_STATUS; + } + catch (Exception e) + { + error("Unknown exception while starting the emulator instance: " + instance + + " Cause: " + e.getMessage()); + + status = new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, e.getMessage()); + } + } + return status; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/stop/AndroidEmulatorStopper.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/stop/AndroidEmulatorStopper.java new file mode 100644 index 0000000..4f8395e --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/logic/stop/AndroidEmulatorStopper.java @@ -0,0 +1,188 @@ +/* +* 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.stop; + +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.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.osgi.util.NLS; +import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate; +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.sequoyah.vnc.vncviewer.registry.VNCProtocolRegistry; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.AndroidLogicUtils; +import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; + +/** + * DESCRIPTION: + * This class contains the business layer of the Android + * Emulator stop procedure + * + * RESPONSIBILITY: + * Stop any Android Emulator + * + * COLABORATORS: + * None. + * + * USAGE: + * Use the public method to stop a Android Emulator + */ +public class AndroidEmulatorStopper +{ + private static Lock lock = new ReentrantReadWriteLock().writeLock(); + + private static final int MAX_LOCK_ATTEMPTS = 20; + + /** + * Stops this instance, removing its viewer from Android Emulator View + * + * @param instance The device instance + * @param force If true do not ask the user if he/she wants to proceed + * @param monitor A progress monitor that will show the disposal + * action progress at UI + * + * @return True if the instance was stopped; false if the user chose not to stop it + */ + public static boolean stopInstance(final IAndroidLogicInstance instance, boolean force, + boolean kill, IProgressMonitor monitor) + { + + if (instance == null) + { + error("Could not stop the protocol because the provided instance is null"); + return false; + } + + boolean canProceed = true; + + // decision whether to actually stop or not comes from user + if (!force) + { + + canProceed = + EclipseUtils.showQuestionDialog(EmulatorNLS.GEN_Question, NLS.bind( + EmulatorNLS.QUESTION_AndroidEmulatorStopper_StopEmulatorQuestion, + instance.getName())); + + } + + if (canProceed) + { + int attempts = 0; + boolean locked = lock.tryLock(); + while (!locked && (attempts < MAX_LOCK_ATTEMPTS)) + { + try + { + Thread.sleep(125); + } + catch (InterruptedException e) + { + //Do nothing! + } + locked = lock.tryLock(); + attempts++; + } + + if (locked) + { + if (monitor == null) + { + monitor = new NullProgressMonitor(); + } + + try + { + info("Stopping the Android Emulator instance"); + + monitor + .beginTask(EmulatorNLS.MON_AndroidEmulatorStopper_DisposingInstance, + 200); + monitor.setTaskName(EmulatorNLS.MON_AndroidEmulatorStopper_DisposingInstance); + + // Try to stop the protocol. + // + // This is not critical to the stop instance procedure, but it is + // desirable because the TmL's methods may do some cleanup + // before returning. The loop below tries to stop for some time (and + // give enough time for cleanup as well), but if TmL does not finish + // in an acceptable time, the stop instance procedure continues + + try + { + info("Trying to stop the protocol"); + // Try to implement a TmL independent code + ProtocolHandle handle = instance.getProtocolHandle(); + if (handle != null) + { + PluginProtocolActionDelegate.requestStopProtocol(handle); + + while (PluginProtocolActionDelegate.isProtocolRunning(handle)) + { + Thread.sleep(250); + } + VNCProtocolRegistry.getInstance().unregister(handle); + info("Protocol stopped"); + } + } + catch (Exception e) + { + error("There was an error while trying to stop the protocol"); + } + + // Try to implement a TmL independent code + instance.setProtocolHandle(null); + + monitor.worked(100); + monitor.setTaskName(EmulatorNLS.MON_AndroidEmulatorStopper_StopVm); + + if (kill) + { + AndroidLogicUtils.kill(instance); + } + + info("Stopped the Android Emulator instance"); + } + finally + { + monitor.done(); + try + { + lock.unlock(); + } + catch (Exception e) + { + warn("The thread that is releasing the lock is not the one which has it."); + } + } + } + else + { + canProceed = false; + } + } + + return canProceed; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/repair/RepairAvdHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/repair/RepairAvdHandler.java new file mode 100644 index 0000000..0a0d6a3 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/repair/RepairAvdHandler.java @@ -0,0 +1,107 @@ +/*
+* 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.service.repair;
+
+import static com.motorola.studio.android.common.log.StudioLogger.error;
+
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.sequoyah.device.framework.model.IInstance;
+import org.eclipse.sequoyah.device.framework.model.handler.IServiceHandler;
+import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler;
+
+import com.android.sdklib.internal.avd.AvdInfo;
+import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
+import com.motorola.studio.android.adt.SdkUtils;
+import com.motorola.studio.android.emulator.EmulatorPlugin;
+import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance;
+import com.motorola.studio.android.emulator.device.refresh.InstancesListRefresh;
+import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
+
+/**
+ * This is responsible for repair damaged avds.
+ */
+public class RepairAvdHandler extends ServiceHandler
+{
+
+ /* (non-Javadoc)
+ * @see org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler#newInstance()
+ */
+ @Override
+ public IServiceHandler newInstance()
+ {
+ return new RepairAvdHandler();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler#runService(org.eclipse.sequoyah.device.framework.model.IInstance, java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ @Override
+ public IStatus runService(IInstance instance, Map<Object, Object> argumentos,
+ IProgressMonitor monitor)
+ {
+ IStatus status = Status.OK_STATUS;
+ if (!(instance instanceof AndroidDeviceInstance))
+ {
+ error(EmulatorNLS.RepairAvdHandler_Not_Android_Instance);
+
+ status =
+ new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID,
+ EmulatorNLS.ERR_StopEmulatorHandler_NotAnAndroidEmulator);
+ }
+ else
+ {
+ AndroidDeviceInstance androidDeviceInstance = (AndroidDeviceInstance) instance;
+ AvdInfo avdInfo = SdkUtils.getVm(androidDeviceInstance.getName());
+ if ((avdInfo != null) && isAvdRepairable(avdInfo.getStatus()))
+ {
+ status = SdkUtils.repairAvd(avdInfo);
+ if (status.getSeverity() != IStatus.OK)
+ {
+ error(getClass(), "IOException ocurred during repairAvd operation"); //$NON-NLS-1$
+ }
+ }
+ else
+ {
+ status =
+ new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID,
+ EmulatorNLS.RepairAvdHandler_AVD_NOT_REPAIRABLE);
+ }
+ }
+ if (status.getSeverity() == IStatus.OK)
+ {
+ InstancesListRefresh.refresh();
+ }
+ return status;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler#updatingService(org.eclipse.sequoyah.device.framework.model.IInstance, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ @Override
+ public IStatus updatingService(IInstance arg0, IProgressMonitor arg1)
+ {
+ return Status.OK_STATUS;
+ }
+
+ private boolean isAvdRepairable(AvdStatus avdStatus)
+ {
+ return avdStatus == AvdStatus.ERROR_IMAGE_DIR;
+ }
+}
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/reset/ResetServiceHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/reset/ResetServiceHandler.java new file mode 100644 index 0000000..9a9618e --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/reset/ResetServiceHandler.java @@ -0,0 +1,124 @@ +/* +* 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.service.reset; + +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.model.handler.IServiceHandler; +import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; +import com.motorola.studio.android.emulator.logic.reset.AndroidEmulatorReseter; + +/** + * DESCRIPTION: + * This class plugs the reset procedure to a TmL service + * + * RESPONSIBILITY: + * Provide access to the reset feature from TmL device framework + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by Eclipse only + */ +public class ResetServiceHandler extends ServiceHandler +{ + + private boolean userAgreed; + + @Override + public IServiceHandler newInstance() + { + return new ResetServiceHandler(); + } + + @Override + public IStatus runService(IInstance instance, Map<Object, Object> arguments, + IProgressMonitor monitor) + { + + boolean force = false; + + if (arguments != null) + { + Object forceObj = arguments.get(EmulatorPlugin.FORCE_ATTR); + if (forceObj instanceof Boolean) + { + force = ((Boolean) forceObj).booleanValue(); + } + } + + IStatus status = Status.OK_STATUS; + if (!force && !userAgreed) + { + status = Status.CANCEL_STATUS; + } + + if (status.isOK() && instance instanceof IAndroidLogicInstance) + { + status = AndroidEmulatorReseter.resetInstance((IAndroidLogicInstance) instance); + } + + // Collecting usage data for statistical purposes + try + { + StudioLogger.collectUsageData(StudioLogger.WHAT_EMULATOR_RESET, + StudioLogger.KIND_EMULATOR, StudioLogger.DESCRIPTION_DEFAULT, + EmulatorPlugin.PLUGIN_ID, EmulatorPlugin.getDefault().getBundle().getVersion() + .toString()); + } + catch (Throwable e) + { + //Do nothing, but error on the log should never prevent app from working + } + return status; + } + + @Override + public IStatus updatingService(IInstance instance, IProgressMonitor monitor) + { + StudioLogger.info("Updating reset service"); + return Status.OK_STATUS; + } + + public IStatus singleInit(List<IInstance> instances) + { + int reset = + EclipseUtils.showInformationDialog(EmulatorNLS.GEN_Warning, + EmulatorNLS.QUESTION_AndroidEmulatorReseter_ConfirmationText, new String[] + { + EmulatorNLS.QUESTION_AndroidEmulatorReseter_Yes, + EmulatorNLS.QUESTION_AndroidEmulatorReseter_No + }, MessageDialog.WARNING); + userAgreed = reset == Dialog.OK; + + return Status.OK_STATUS; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/start/StartEmulatorHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/start/StartEmulatorHandler.java new file mode 100644 index 0000000..a7b3085 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/start/StartEmulatorHandler.java @@ -0,0 +1,128 @@ +/* +* 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.service.start; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; +import static com.motorola.studio.android.common.log.StudioLogger.error; + +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.model.handler.IServiceHandler; +import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.AndroidLogicUtils; +import com.motorola.studio.android.emulator.logic.start.AndroidEmulatorStarter; + +/** + * DESCRIPTION: This class handles the start Android Emulator action, which + * transitions from both stopped states to started + * + * RESPONSIBILITY: Delegate the action to the AndroidEmulatorStarter, which + * contains the business layer logic for starting Android Emulators. + * + * COLABORATORS: None. + * + * USAGE: This class is intended to be used by Eclipse only + */ +public class StartEmulatorHandler extends ServiceHandler +{ + + /** + * @see IServiceHandler#newInstance() + */ + @Override + public IServiceHandler newInstance() + { + return new StartEmulatorHandler(); + } + + /** + * @see ServiceHandler#runService(IInstance, Map, IProgressMonitor) + */ + @Override + public IStatus runService(IInstance instance, Map<Object, Object> arguments, + IProgressMonitor monitor) + { + IStatus status = Status.OK_STATUS; + try + { + + String description = ""; + + // Tests if the given instance is a Android instance + if (!(instance instanceof AndroidDeviceInstance)) + { + error("Aborting start service. This is not an Android Emulator instance..."); + status = + new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, + EmulatorNLS.ERR_StartEmulatorHandler_NotAnAndroidEmulator); + } + else + { + description = ((AndroidDeviceInstance) instance).getTarget(); + try + { + AndroidLogicUtils.testCanceled(monitor); + + status = + AndroidEmulatorStarter.startInstance((AndroidDeviceInstance) instance, + arguments, monitor); + + debug("Finished Execution of the start emulator service... :" + instance + + " Status => " + status); + } + catch (StartCancelledException e) + { + monitor.done(); + status = Status.CANCEL_STATUS; + } + } + + description = StudioLogger.KEY_TARGET + description; + StudioLogger.collectUsageData(StudioLogger.WHAT_EMULATOR_START, + StudioLogger.KIND_EMULATOR, description, EmulatorPlugin.PLUGIN_ID, + EmulatorPlugin.getDefault().getBundle().getVersion().toString()); + } + catch (Throwable t) + { + StudioLogger.error(StartEmulatorHandler.class.toString(), + "An exception ocurred during emulator start up process.", t); + status = + new Status(Status.ERROR, EmulatorPlugin.PLUGIN_ID, + "An exception ocurred during emulator start up process."); + } + return status; + } + + /** + * @see ServiceHandler#updatingService(IInstance, IProgressMonitor) + */ + @Override + public IStatus updatingService(IInstance instance, IProgressMonitor monitor) + { + return Status.OK_STATUS; + } + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/stop/StopEmulatorCommand.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/stop/StopEmulatorCommand.java new file mode 100644 index 0000000..60162eb --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/stop/StopEmulatorCommand.java @@ -0,0 +1,47 @@ +/* +* 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.service.stop; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.sequoyah.device.common.utilities.exception.SequoyahException; +import org.eclipse.sequoyah.device.framework.model.IInstance; + +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; + +public class StopEmulatorCommand extends AbstractHandler +{ + + public Object execute(ExecutionEvent event) throws ExecutionException + { + IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + if (instance instanceof IInstance) + { + try + { + EmulatorPlugin.getStopServiceHandler().run((IInstance) instance); + } + catch (SequoyahException e) + { + //do nothing + } + } + return null; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/stop/StopEmulatorHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/stop/StopEmulatorHandler.java new file mode 100644 index 0000000..b96706d --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/service/stop/StopEmulatorHandler.java @@ -0,0 +1,130 @@ +/* +* 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.service.stop; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; +import static com.motorola.studio.android.common.log.StudioLogger.error; + +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent; +import org.eclipse.sequoyah.device.framework.events.InstanceEventManager; +import org.eclipse.sequoyah.device.framework.events.InstanceEvent.InstanceEventType; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.device.framework.model.handler.IServiceHandler; +import org.eclipse.sequoyah.device.framework.model.handler.ServiceHandler; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.device.instance.AndroidDeviceInstance; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.stop.AndroidEmulatorStopper; + +/** + * DESCRIPTION: + * This class plugs the stop procedure to a TmL service + * + * RESPONSIBILITY: + * Provide access to the stop feature from TmL device framework + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by Eclipse only + */ +public class StopEmulatorHandler extends ServiceHandler +{ + @Override + public IServiceHandler newInstance() + { + return new StopEmulatorHandler(); + } + + @Override + public IStatus runService(IInstance instance, Map<Object, Object> arguments, + IProgressMonitor monitor) + { + debug("Executing the stop emulator service... Instance:" + instance + " Arguments:" + + arguments); + + IStatus status = Status.OK_STATUS; + + // actually stop emulator + if (!(instance instanceof AndroidDeviceInstance)) + { + error("Aborting start service. This is not an Android Emulator instance..."); + + status = + new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, + EmulatorNLS.ERR_StopEmulatorHandler_NotAnAndroidEmulator); + } + else + { + boolean force = false; + if (arguments != null) + { + Object forceObj = arguments.get(EmulatorPlugin.FORCE_ATTR); + if (forceObj instanceof Boolean) + { + force = ((Boolean) forceObj).booleanValue(); + } + } + + boolean stopPerformed = + AndroidEmulatorStopper.stopInstance((AndroidDeviceInstance) instance, force, + true, monitor); + + if (!stopPerformed) + { + // user decided not to stop; return a cancel status + status = Status.CANCEL_STATUS; + } + else + { + instance.setNameSuffix(null); + InstanceEventManager.getInstance().notifyListeners( + new InstanceEvent(InstanceEventType.INSTANCE_UPDATED, instance)); + } + } + + debug("Finished the execution of the stop emulator service: " + instance + " status: " + + status); + + // Collecting usage data for statistical purposes + try + { + StudioLogger.collectUsageData(StudioLogger.WHAT_EMULATOR_STOP, + StudioLogger.KIND_EMULATOR, status.toString(), EmulatorPlugin.PLUGIN_ID, + EmulatorPlugin.getDefault().getBundle().getVersion().toString()); + } + catch (Throwable e) + { + //Do nothing, but error on the log should never prevent app from working + } + + return status; + } + + @Override + public IStatus updatingService(IInstance instance, IProgressMonitor monitor) + { + return Status.OK_STATUS; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/AndroidSkin.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/AndroidSkin.java new file mode 100644 index 0000000..2eff106 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/AndroidSkin.java @@ -0,0 +1,428 @@ +/* +* 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.skin.android; + +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.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.skin.AndroidSkinBean; +import com.motorola.studio.android.emulator.core.skin.IAndroidKey; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.ISkinKeyXmlTags; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.skin.android.parser.LayoutFileModel; +import com.motorola.studio.android.emulator.skin.android.parser.LayoutFileParser; + +public class AndroidSkin implements IAndroidSkin +{ + /** + * The released images of the skin, as read from the skin files + */ + private final Map<String, ImageData> releasedImagesPool = new HashMap<String, ImageData>(); + + /** + * The pressed images of the skin, as read from the skin files + */ + private final Map<String, ImageData> pressedImagesPool = new HashMap<String, ImageData>(); + + /** + * The enter images of the skin, as read from the skin files + */ + private final Map<String, ImageData> enterImagesPool = new HashMap<String, ImageData>(); + + /** + * The keys of the skin, as read from the skin files + */ + private final Map<String, Collection<IAndroidKey>> androidKeysPool = + new HashMap<String, Collection<IAndroidKey>>(); + + /** + * The folder where to look for skin files + */ + private File skinFilesPath; + + /** + * A model containing the parsed layout file + */ + private LayoutFileModel layoutFile; + + private Properties keycodes; + + /** + * Retrieves data being kept in a pool + * + * @param layoutName The name of the layout which object has been requested + * @param pool The pool where to look for data + * + * @return An object from the pool that matches the request + * + * @throws SkinException If either the layout file was not loaded, or if the images/keys cannot + * be generated + */ + private ImageData getImageFromPool(String layoutName, Map<String, ImageData> pool) + throws SkinException + { + if (layoutFile == null) + { + error("User has tried to request skin data without setting a valid skin files path"); + throw new SkinException(EmulatorNLS.ERR_AndroidSkin_NoLayoutLoaded); + } + + // Tries to get data from the pool. If it is not available for the + // current layout yet, load all resources related to the layout to the pools. + ImageData id = pool.get(layoutName); + + if (id == null) + { + + addImagesToPools(layoutName); + id = pool.get(layoutName); + } + + return id; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getKeyDataCollection(java.lang.String) + */ + public Collection<IAndroidKey> getKeyDataCollection(String layoutName) + { + Collection<IAndroidKey> androidKeys; + try + { + if (layoutFile == null) + { + error("User has tried to request skin data without setting a valid skin files path"); + throw new SkinException(EmulatorNLS.ERR_AndroidSkin_NoLayoutLoaded); + } + + // Tries to get data from the pool. If it is not available for the + // current layout yet, load all resources related to the layout to the pools. + androidKeys = androidKeysPool.get(layoutName); + + if (androidKeys == null) + { + + System.gc(); + androidKeys = + AndroidSkinTranslator.generateAndroidKeys(layoutFile, layoutName, + skinFilesPath); + System.gc(); + androidKeysPool.put(layoutName, androidKeys); + } + } + catch (SkinException e) + { + androidKeys = new HashSet<IAndroidKey>(); + error("The key data could not be retrieved from skin files. Cause: " + e.getMessage()); + EclipseUtils.showErrorDialog(e); + } + + return androidKeys; + } + + public Properties getKeyCodes() + { + if ((keycodes == null) || ((keycodes != null) && keycodes.isEmpty())) + { + try + { + keycodes = AndroidSkinTranslator.getKeycodes(layoutFile, skinFilesPath); + } + catch (SkinException e) + { + keycodes = new Properties(); + error("The key data could not be retrieved from skin files. Cause: " + + e.getMessage()); + } + } + + return keycodes; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getPressedImageData(java.lang.String) + */ + public ImageData getPressedImageData(String layoutName) throws SkinException + { + return getImageFromPool(layoutName, pressedImagesPool); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getEnterImageData(java.lang.String) + */ + public ImageData getEnterImageData(String layoutName) throws SkinException + { + return getImageFromPool(layoutName, enterImagesPool); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getReleasedImageData(java.lang.String) + */ + public ImageData getReleasedImageData(String layoutName) throws SkinException + { + return getImageFromPool(layoutName, releasedImagesPool); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getSkinBean(java.lang.String) + */ + public AndroidSkinBean getSkinBean(String layoutName) throws SkinException + { + if (layoutFile == null) + { + error("User has tried to request additional skin data without setting a valid skin files path"); + throw new SkinException(EmulatorNLS.ERR_AndroidSkin_NoLayoutLoaded); + } + + AndroidSkinBean bean = new AndroidSkinBean(); + + // Fills the skin bean with information related to the part chosen for display. This bean must have + // at least display positioning information and scale. + String partName = layoutFile.getMainPartName(layoutName); + Point dPosition = + AndroidSkinTranslator.translateDisplayPosition(layoutFile, layoutName, partName, + skinFilesPath); + int dWidth = layoutFile.getDisplayWidth(partName); + int dHeight = layoutFile.getDisplayHeight(partName); + + int dw, dh; + if (layoutFile.isSwapWidthHeightNeededAtLayout(layoutName)) + { + dw = dHeight; + dh = dWidth; + } + else + { + dw = dWidth; + dh = dHeight; + } + + bean.addSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_X, dPosition.x); + bean.addSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_Y, dPosition.y); + bean.addSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH, dw); + bean.addSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT, dh); + bean.addSkinPropertyValue(ISkinKeyXmlTags.SKIN_EMBEDDED_VIEW_SCALE, 10); + + return bean; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#isFlipSupported() + */ + public boolean isFlipSupported() + { + return false; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#setSkinFilesPath(java.lang.String) + */ + public void setSkinFilesPath(String skinFilesPath) throws SkinException + { + + this.skinFilesPath = new File(skinFilesPath); + if (this.skinFilesPath.isDirectory()) + { + + layoutFile = LayoutFileParser.readLayout(this.skinFilesPath); + + } + else + { + error("Provided skin files path is not a directory. Setting the skin files path operation has failed."); + throw new SkinException(EmulatorNLS.ERR_AndroidSkin_ProvidedSkinPathIsNotADirectory); + } + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#isRotatedLayout(java.lang.String) + */ + public boolean isSwapWidthHeightNeededAtLayout(String layoutName) + { + return layoutFile.isSwapWidthHeightNeededAtLayout(layoutName); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getAvailableLayouts() + */ + public Collection<String> getAvailableLayouts() + { + return layoutFile.getLayoutNames(); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getLayoutScreenCommand(java.lang.String) + */ + public String getLayoutScreenCommand(String layoutName) + { + return layoutFile.getLayoutSwitchCommand(layoutName); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#nextLayout(java.lang.String) + */ + public String getNextLayout(String referenceLayout) + { + info("Calculating the next layout"); + String next = null; + if (referenceLayout != null) + { + List<String> layoutsList = new ArrayList<String>(getAvailableLayouts()); + + // Switches to the next layout if the skin supports it. The if/else clause + // implements a circular list + if (layoutsList.size() > 1) + { + int currentLayoutNum = layoutsList.indexOf(referenceLayout); + if (currentLayoutNum != layoutsList.size() - 1) + { + next = layoutsList.get(++currentLayoutNum); + } + else + { + next = layoutsList.get(0); + } + info("Next layout: " + next); + } + else + { + warn("The skin doesn't have multiple layouts. The operation was not performed"); + } + } + else + { + warn("The skin doesn't have multiple layouts. The operation was not performed"); + } + + return next; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#previousLayout(java.lang.String) + */ + public String getPreviousLayout(String referenceLayout) + { + info("Calculating the previous layout"); + String previous = null; + if (referenceLayout != null) + { + List<String> layoutsList = new ArrayList<String>(getAvailableLayouts()); + + // Switches to the previous layout if the skin supports it. The if/else clause + // implements a circular list + if (layoutsList.size() > 1) + { + int currentLayoutNum = layoutsList.indexOf(referenceLayout); + + if (currentLayoutNum != 0) + { + previous = layoutsList.get(--currentLayoutNum); + } + else + { + previous = layoutsList.get(layoutsList.size() - 1); + } + info("Previous layout: " + previous); + } + else + { + warn("The skin doesn't have multiple layouts. The operation was not performed"); + } + } + else + { + warn("The skin doesn't have multiple layouts. The operation was not performed"); + } + + return previous; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getBackgroundColor(java.lang.String) + */ + public RGB getBackgroundColor(String layoutName) + { + return layoutFile.getLayoutColor(layoutName, skinFilesPath); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.core.skin.IAndroidSkin#getDpadRotation(java.lang.String) + */ + public int getDpadRotation(String layoutName) + { + return layoutFile.getDpadRotation(layoutName); + } + + /** + * Generates images/keys for the current layout (or main part, if a layout is not available) + * and store them at the appropriate pools + * + * @param key The key to be used to store data at the pools + * + * @throws SkinException If the layout file cannot be loaded + */ + private void addImagesToPools(String key) throws SkinException + { + // Validates the provided string before proceeding + if ((key == null) || (!layoutFile.getLayoutNames().contains(key))) + { + throw new SkinException(EmulatorNLS.ERR_AndroidSkin_InvalidLayoutProvided); + } + + System.gc(); + + ImageData[] images = + AndroidSkinTranslator.generateLayoutImages(layoutFile, key, skinFilesPath); + + info("Adding released/pressed/hover images to the pool"); + releasedImagesPool.put(key, images[0]); + pressedImagesPool.put(key, images[1]); + enterImagesPool.put(key, images[2]); + + System.gc(); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/AndroidSkinTranslator.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/AndroidSkinTranslator.java new file mode 100644 index 0000000..9e0ee89 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/AndroidSkinTranslator.java @@ -0,0 +1,1370 @@ +/* +* 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.skin.android; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Properties; + +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Shell; + +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.skin.AndroidPressKey; +import com.motorola.studio.android.emulator.core.skin.IAndroidKey; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.skin.android.parser.LayoutFileModel; + +/** + * DESCRIPTION: + * Utility class for translating Google skin files data into objects + * suitable to the viewer. This includes: + * - Generating image data objects in memory that represent the exact images that + * will be drawn using SWT Image/GC/Transform graphic objects. It is necessary to + * create new image data objects from scratch because the operations performed by + * Image/GC/Transform affects only the display, keeping the associated image data + * objects intact. It would also be very expensive to generate those images at + * screen, after every mouse event (including mouse moves, which are frequent) + * - Generating a key data collection suitable for a given layout. The key data + * objects must contain positioning information that depends on the position of + * elements inside the layout. It is this class job to discover what are the + * coordinates to set at the keys + * - Translate the display position for a given layout. As with the key data + * collection, the coordinates of the display depends on the layout description + * and must be calculated. + * + * RESPONSIBILITY: + * Provide translation functionalities to convert Google format to a format + * suitable to the viewer + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by AndroidSkin class only + * + * + * + * This class uses several concepts, that are described below: + * + * LAYOUT FILE MODEL: A representation of the file "layout", saved as part + * of the skin in the skin folder. Attributes such as keyboard charmap and + * network are global to a layout file + * LAYOUT: A collection of parts, with some part integration features, such + * as part positioning and part rotation. Parts can be rotated independently + * in a layout. + * PART: The minimum skin view. It contains buttons and background image. + * A display is optional + * OFFSET: If a part is declared to be drawn in a layout using negative coordinates, + * or if a button is declared to be drawn in a part using negative coordinates, + * the calculated layout/part offset is different from 0 in either (x, y) directions. + * If such situation happen, it is mandatory to generate a bigger image in memory + * to have the layout/part drawn. + * EXPANDED PART IMAGE: Is how is called the image generated in memory due to part + * offset being different from zero. Layouts always have images generated in memory, + * but parts can be represented as a simple file load if no button demands an offset + * different from (0, 0) + * + * Considerations about the differences between Google format and viewer format: + * + * a) Google format supports negative coordinates for parts/buttons. To generate images + * that are renderable by SWT, the viewer module must translate the parts/buttons so + * that they fit in a single image data + * b) Viewer demands a pair of pressed, released and enter images and a set of IAndroidKeys. + * Google provides several images to use as filter when a button is pressed. To increase + * the performance, all the images/keys are generated during instance start time, being + * reused afterwards + * c) In Google format, the part position at the layout is ALWAYS set to the upper-left + * corner of the PART, not the layout. Therefore, depending on the rotation parameter + * we need to calculate what is the position to draw the part relative to the layout + * + * --------------------------- + * | | 1) OLX / OLY : Offset due to layout, if one or more part + * | ------------------ | position coordinates are less than 0 + * | | ------------ | | 2) OPX / OPY : Offset due to part, if one of more button + * | | | | | | position coordinates are less than 0 + * | |OPX| | | | 3) LAYOUT : Layout image area + * | | ----- | | | 4) PART : Part expanded image area + * | | |BTN| | | | 5) BACKGR : Original part background image, which demanded + * | | | | | | | expansion due to BTN + * | | ----- | | | 6) BTN : A button with negative x coordinate related to + * | | | BACKGR | | | BACKGR + * | | ------------ | | + * | | PART | | + * | OLX ------------------ | + * | LAYOUT | + * --------------------------- + * + */ +public class AndroidSkinTranslator +{ + /** + * Constant that describes a QWERTY charmap in the layout + */ + private static final String CHARMAP_QWERTY = "qwerty"; + + /** + * Constant that describes a QWERTY2 charmap in the layout + */ + private static final String CHARMAP_QWERTY2 = "qwerty2"; + + /** + * Path to the QWERTY codes inside the plugin + */ + private static final String CHARMAP_QWERTY_FILE = "res/qwerty.properties"; + + /** + * Constant that describes a AVRCP charmap in the layout + */ + private static final String CHARMAP_AVRCP = "AVRCP"; + + /** + * Path to the AVRCP codes inside the plugin + */ + private static final String CHARMAP_AVRCP_FILE = "res/AVRCP.properties"; + + private static final String CHARMAP_OPHONE_QWERTY_FILE = "res/ophone_qwerty.properties"; + + private static final String CHARMAP_OPHONE_AVRCP_FILE = "res/ophone_AVRCP.properties"; + + /** + * d-pad keys + */ + private static final String DPAD_DOWN = "DPAD_DOWN"; + + private static final String DPAD_UP = "DPAD_UP"; + + private static final String DPAD_LEFT = "DPAD_LEFT"; + + private static final String DPAD_RIGHT = "DPAD_RIGHT"; + + /** + * Translates the button information inside the layout file model into + * viewer compatible IAndroidKeys + * <br><br> + * @param layoutFile The model that represent the layout file + * @param layoutName The name of the layout where to look for buttons in the model, + * or <code>null</code> if no layout is available + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return A collection of IAndroidKeys, describing the keys, codes and positions + * of the buttons in the skin + * <br><br> + * @throws SkinException If an error that prevents the translation happens, such as + * errors when opening required files or charmap not supported + */ + static Collection<IAndroidKey> generateAndroidKeys(LayoutFileModel layoutFile, + String layoutName, File skinFilesPath) throws SkinException + { + + // Retrieve a map of keycodes related to the keyboard declared at the layout + Collection<IAndroidKey> keyCollection = new LinkedHashSet<IAndroidKey>(); + + Properties keycodes = getKeycodes(layoutFile, skinFilesPath); + + // Retrieve the names of the parts that compose the layout + Collection<String> partNames = layoutFile.getLayoutPartNames(layoutName); + + // Gets the layout offset for position adjustments + Point offsetL = getLayoutOffset(layoutFile, layoutName, skinFilesPath); + + // Iterates on the parts, looking for their buttons + + for (String partName : partNames) + { + + Point offsetP = getPartOffset(layoutFile, partName); + Point partSize = getPartImageSize(layoutFile, partName, skinFilesPath, offsetP); + + // Iterates on the buttons of the part, creating an IAndroidKey for each in the end + Collection<String> buttonNames = layoutFile.getButtonNames(partName); + + for (String buttonName : buttonNames) + { + String k = buttonName.toUpperCase().replace("-", "_"); + String keyCodeStr = (String) keycodes.get(k); + + // The IAndroidKey will only be generated if a keycode with the same name is found + if (keyCodeStr != null) + { + + // Retrieve the button parameters needed for the key generation + // - The button position must be translated due to the layout and eventual offsets + // - The button w/h must be interpreted according to the rotation parameter. If the + // rotation is odd (landscape), the button width must be the key height and vice-versa. + // Otherwise (even, portrait), we can use the button w/h at the keys as is. + int buttonW = layoutFile.getButtonWidth(partName, buttonName, skinFilesPath); + int buttonH = layoutFile.getButtonHeight(partName, buttonName, skinFilesPath); + Point buttonPos = + translateButtonPosition(layoutFile, layoutName, partName, buttonName, + skinFilesPath, offsetP, partSize); + + int startX = offsetL.x + buttonPos.x; + int startY = offsetL.y + buttonPos.y; + int endX, endY; + + if (layoutFile.isSwapWidthHeightNeededAtLayout(layoutName, partName)) + { + endX = startX + buttonH; + endY = startY + buttonW; + } + else + { + endX = startX + buttonW; + endY = startY + buttonH; + } + + int dpadRotation = layoutFile.getDpadRotation(layoutName); + + keyCodeStr = getRotatedKeyCode(k, keyCodeStr, dpadRotation, keycodes); + + AndroidPressKey key = + new AndroidPressKey(buttonName, keyCodeStr, buttonName, startX, startY, + endX, endY, "", 0); + + keyCollection.add(key); + + } + } + + } + + return keyCollection; + } + + /** + * @param keyCodeStr + * @param keyCodeStr2 + * @param dpadRotation + * @param keycodes + * @return + */ + private static String getRotatedKeyCode(String keyName, String keyCodeStr, int dpadRotation, + Properties keycodes) + { + String keyCode = keyCodeStr; + switch (dpadRotation % 4) + { + case 1: + if (DPAD_DOWN.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_RIGHT); + } + else if (DPAD_LEFT.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_DOWN); + } + else if (DPAD_RIGHT.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_UP); + } + else if (DPAD_UP.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_LEFT); + } + break; + case 2: + if (DPAD_DOWN.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_UP); + } + else if (DPAD_LEFT.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_RIGHT); + } + else if (DPAD_RIGHT.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_LEFT); + } + else if (DPAD_UP.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_DOWN); + } + break; + case 3: + if (DPAD_DOWN.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_LEFT); + } + else if (DPAD_LEFT.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_UP); + } + else if (DPAD_RIGHT.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_DOWN); + } + else if (DPAD_UP.equals(keyName)) + { + keyCode = (String) keycodes.get(DPAD_RIGHT); + } + break; + default: + //Does nothing, no rotation needed. + break; + } + return keyCode; + } + + + /** + * Merge two ImageData objects and return the merge. + * <br><br> + * @param srcData The source ImageData + * @param dstData The destination ImageData + * @param dstX The x position in dstData where srcData will be merged + * @param dstY The y position in dstData where srcData will be merged + * <br><br> + * @return An array containing released and pressed image data at the positions 0 and 1, respectively + */ + static ImageData mergeImageGC(ImageData srcData, ImageData dstData, int dstX, int dstY) { + + Shell s = new Shell(); + + Image srcImg = new Image(s.getDisplay(), srcData); + Image dstImg = new Image(s.getDisplay(), dstData); + + GC gc = new GC(dstImg); + gc.drawImage(srcImg, dstX, dstY); + gc.dispose(); + + return dstImg.getImageData(); + + } + + /** + * Creates the layout images for released and pressed buttons + * <br><br> + * @param layoutFile The model that represents the layout file + * @param layoutName The name of the layout where to look for images in the model + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return An array containing released and pressed image data at the positions 0 and 1, respectively + */ + static ImageData[] generateLayoutImages(LayoutFileModel layoutFile, String layoutName, + File skinFilesPath) + { + + ImageData[] layoutImgs = new ImageData[3]; + + // Gets the layout offset for position adjustments + Point offsetL = getLayoutOffset(layoutFile, layoutName, skinFilesPath); + + // Iterates on the names of the parts that compose the layout + Collection<String> partNames = layoutFile.getLayoutPartNames(layoutName); + + for (String partName : partNames) + { + + if (!layoutFile.partHasBg(partName)) + { + continue; + } + + if (partName.equals("portrait") || partName.equals("landscape")) + { + if (!partName.equals(layoutName)) + { + continue; + } + } + + // Gets the part offset for position adjustments and images loading decision + Point offsetP = getPartOffset(layoutFile, partName); + Point partSize = getPartImageSize(layoutFile, partName, skinFilesPath, offsetP); + int bgH = layoutFile.getBackgroundHeight(partName, skinFilesPath); + int bgW = layoutFile.getBackgroundWidth(partName, skinFilesPath); + + // Loads the part images for released and pressed. If there is a part offset, it is needed + // to generated an expanded part image data + ImageData[] bgDatas = new ImageData[3]; + if ((offsetP.x > 0) || (offsetP.y > 0) || (partSize.x > offsetP.x + bgW) + || (partSize.y > offsetP.y + bgH)) + { + bgDatas[0] = + generateExpandedPartImageData(layoutFile, layoutName, partName, offsetP, + skinFilesPath, offsetP, partSize); + } + else + { + bgDatas[0] = getImageData(layoutFile, partName, null, skinFilesPath); + } + + bgDatas[1] = + generateMergedWithButtonsImage(layoutFile, (ImageData) bgDatas[0].clone(), + partName, skinFilesPath, offsetP, false); + + bgDatas[2] = + generateMergedWithButtonsImage(layoutFile, (ImageData) bgDatas[0].clone(), + partName, skinFilesPath, offsetP, true); + + // Loop for generating layout images based on the part images above + for (int img = 0; img < 3; img++) + { + // A layout image is created only if it wasn't yet, no matter how many parts the layout has. + if (layoutImgs[img] == null) + { + Point layoutSize = + getLayoutImageSize(layoutFile, layoutName, skinFilesPath, offsetL); + layoutImgs[img] = + generateImageDataWithBackground(layoutFile, layoutName, layoutSize.x, + layoutSize.y, bgDatas[img], skinFilesPath); + + } + + // Copy the part image pixels into the layout image + // - Rotation units rotates the image in CLOCKWISE direction + // - The part position must be translated because: + // a) At Google layout file, the (x,y) position represents the upper left corner of the + // part image, no matter what is the rotation value + // b) We are generating a layout image referenced at the upper left corner of the layout + // image, and we must know where we must place the part image in terms of the layout reference + // - The part is rotated before merged to the layout image + int rotation = layoutFile.getPartRotationAtLayout(layoutName, partName); + Point partPos = + translatePartPosition(layoutFile, layoutName, partName, skinFilesPath, + offsetP, partSize); + bgDatas[img] = generateRotatedImage(bgDatas[img], rotation); + + int startX = offsetL.x - offsetP.x + partPos.x; + int startY = offsetL.y - offsetP.y + partPos.y; + ImageData merge = mergeImageGC(bgDatas[img], layoutImgs[img], startX, startY); + layoutImgs[img] = merge; + + + } + } + + return layoutImgs; + } + + + + /** + * Translates the display information from the layout file into an Point referenced at the + * upper left corner of the layout image (or part image, if layouts are not supported by the skin) + * <br><br> + * @param layoutFile The model that represents the layout file + * @param layoutName The name of the layout being used + * @param partName The name of the part which contains the display + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return A point referenced at the upper left corner of the layout image, where to draw the display + */ + static Point translateDisplayPosition(LayoutFileModel layoutFile, String layoutName, + String partName, File skinFilesPath) + { + // Gets the parameters necessary for calculation + Point displayPos = layoutFile.getDisplayPosition(partName); + int displayW = layoutFile.getDisplayWidth(partName); + int displayH = layoutFile.getDisplayHeight(partName); + Point offsetP = getPartOffset(layoutFile, partName); + Point partSize; + if (!layoutFile.partHasBg(partName)) + { + partSize = getPartImageSize(layoutFile, layoutName, skinFilesPath, offsetP); + } + else + { + partSize = getPartImageSize(layoutFile, partName, skinFilesPath, offsetP); + } + + // Update the display position, considering part offset/size and rotation + displayPos = + translatePartElementPosition(layoutFile, layoutName, partName, skinFilesPath, + displayPos, displayW, displayH, offsetP, partSize); + + // Adjusts the position (according to the layout offset) and returns to the caller + Point offsetL = getLayoutOffset(layoutFile, layoutName, skinFilesPath); + displayPos.x += offsetL.x; + displayPos.y += offsetL.y; + + return displayPos; + } + + /** + * Retrieves the keycodes to be used at the keys. + * Uses as parameter the keyboard charmap declared at the layout file model + * <br><br> + * @param layoutFile The model that represents the layout file + * <br><br> + * @return A map of properties containing the keycodes for each key of the charmap + * declared at the layout file + * <br><br> + * @throws SkinException If the keycode file cannot be loaded + */ + static Properties getKeycodes(LayoutFileModel layoutFile, File skinFilesPath) + throws SkinException + { + String charmap = layoutFile.getKeyboardCharmap(); + Properties keycodes = new Properties(); + InputStream is = null; + URL url = null; + try + { + // If nothing is specified, use the QWERTY charmap + // If it is specified QWERTY or QWERTY2, use QWERTY charmap too + if ((charmap == null) || (charmap.equals(CHARMAP_QWERTY)) + || (charmap.equals(CHARMAP_QWERTY2))) + { + + url = + EmulatorPlugin + .getDefault() + .getBundle() + .getResource( + SdkUtils.isOphoneSDK() ? CHARMAP_OPHONE_QWERTY_FILE + : CHARMAP_QWERTY_FILE); + } + else if (charmap.equals(CHARMAP_AVRCP)) + { + url = + EmulatorPlugin + .getDefault() + .getBundle() + .getResource( + SdkUtils.isOphoneSDK() ? CHARMAP_OPHONE_AVRCP_FILE + : CHARMAP_AVRCP_FILE); + } + else + { + warn("The skin at " + skinFilesPath.getAbsolutePath() + + " does not use a supported charmap"); + return keycodes; + } + + is = url.openStream(); + keycodes.load(is); + } + catch (IOException e) + { + error("There was an error reading the file " + url); + throw new SkinException(EmulatorNLS.ERR_AndroidSkinTranslator_ErrorReadingKeycodeFile); + } + finally + { + try + { + is.close(); + } + catch (IOException e) + { + StudioLogger.error("Could not close input stream: ", e.getMessage()); //$NON-NLS-1$ + } + } + + return keycodes; + } + + public static Properties getQwertyKeyMap() + { + Properties keycodes = new Properties(); + URL url = + EmulatorPlugin + .getDefault() + .getBundle() + .getResource( + SdkUtils.isOphoneSDK() ? CHARMAP_OPHONE_QWERTY_FILE + : CHARMAP_QWERTY_FILE); + + InputStream in; + try + { + in = url.openStream(); + keycodes.load(in); + } + catch (IOException e) + { + error("There was an error reading the file " + url); + } + + return keycodes; + + } + + /** + * Calculates and retrieves the layout offset. + * The offset is different from (0, 0) if any part has negative coordinates (x or y) according to the + * specification of the layout file + * <br><br> + * @param layoutFile The model that represents the layout file + * @param layoutName The layout that we wish to have the offset calculated + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return The layout offset + */ + private static Point getLayoutOffset(LayoutFileModel layoutFile, String layoutName, + File skinFilesPath) + { + int minX = 0; + int minY = 0; + + Collection<String> partNames = layoutFile.getLayoutPartNames(layoutName); + + for (String partName : partNames) + { + if (partName.equals("portrait") || partName.equals("landscape")) + { + if (!partName.equals(layoutName)) + { + continue; + } + } + + Point offsetP = getPartOffset(layoutFile, partName); + + Point partSize; + if (layoutFile.partHasBg(partName)) + { + partSize = getPartImageSize(layoutFile, partName, skinFilesPath, offsetP); + } + else + { + continue; + // partSize = getPartImageSize(layoutFile, layoutName, skinFilesPath, offsetP); + } + + // The part position needs translation because its coordinates may change due to + // the buttons position (if the part offset is different from (0, 0)) + Point partPos = + translatePartPosition(layoutFile, layoutName, partName, skinFilesPath, offsetP, + partSize); + if (partPos.x < minX) + { + minX = partPos.x; + } + if (partPos.y < minY) + { + minY = partPos.y; + } + } + + Point layoutOffset = new Point(Math.abs(minX), Math.abs(minY)); + + return layoutOffset; + } + + /** + * Calculates and retrieves a part offset. + * The offset is different from (0, 0) if any button that belong to the part has negative coordinates + * (x or y) according to the specification of the layout file + * <br><br> + * @param layoutFile The model that represents the layout file + * @param partName The part that we wish to have the offset calculated + * <br><br> + * @return The part offset + */ + private static Point getPartOffset(LayoutFileModel layoutFile, String partName) + { + int minX = 0; + int minY = 0; + + Collection<String> buttonNames = layoutFile.getButtonNames(partName); + + for (String buttonName : buttonNames) + { + Point buttonPos = layoutFile.getButtonPosition(partName, buttonName); + if (buttonPos.x < minX) + { + minX = buttonPos.x; + } + if (buttonPos.y < minY) + { + minY = buttonPos.y; + } + } + + Point offset = new Point(Math.abs(minX), Math.abs(minY)); + + return offset; + } + + /** + * Retrieves the size of the layout image. + * <br><br> + * @param layoutFile The model that represents the layout file + * @param layoutName The name of the layout to have its size calculated + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return The size of the layout image + */ + private static Point getLayoutImageSize(LayoutFileModel layoutFile, String layoutName, + File skinFilesPath, Point layoutOffset) + { + int maxX = 0; + int maxY = 0; + + // Iterates on the layout parts, looking for the area required by each of them + + Collection<String> partNames = layoutFile.getLayoutPartNames(layoutName); + for (String partName : partNames) + { + + if (partName.equals("portrait") || partName.equals("landscape")) + { + if (!partName.equals(layoutName)) + { + continue; + } + } + + // The part position must be translated because: + // - At Google layout file, the part (x,y) position represents the upper left corner of the + // PART image, no matter what is the rotation value + // - We are generating a layout image referenced at the upper left corner of the LAYOUT + // image, and we must know where we must place the part image in terms of the layout reference + Point offsetP = getPartOffset(layoutFile, partName); + Point partSize = getPartImageSize(layoutFile, partName, skinFilesPath, offsetP); + Point partPos = + translatePartPosition(layoutFile, layoutName, partName, skinFilesPath, offsetP, + partSize); + if (layoutFile.isSwapWidthHeightNeededAtLayout(layoutName, partName)) + { + // If the part is in landscape direction, we sum the width at y and + // height at x + // + // -------------- + // Y | width | + // | | + // A | h | -------------------------- + // X | e | | height | + // I | i | | w | + // S | g | | i | + // | h | | d | + // | t | | t | + // | | | h ROTATED PART | + // | PART | -------------------------- + // -------------- + // X AXIS + // + maxX = Math.max(maxX, layoutOffset.x + partPos.x + partSize.y); + maxY = Math.max(maxY, layoutOffset.y + partPos.y + partSize.x); + } + else + { + // If the part is in portrait direction, we sum the w/h as is + maxX = Math.max(maxX, layoutOffset.x + partPos.x + partSize.x); + maxY = Math.max(maxY, layoutOffset.y + partPos.y + partSize.y); + } + } + + Point imgSize = new Point(maxX, maxY); + + return imgSize; + } + + /** + * Retrieves the minimum size of the part image. + * <br><br> + * @param layoutFile The model that represents the layout file + * @param partName The name of the part to have its size calculated + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return The minimum size of the part image + */ + private static Point getPartImageSize(LayoutFileModel layoutFile, String partName, + File skinFilesPath, Point partOffset) + { + // The initial maximum is set to be the width/height of the original image + part offset + // + // It will be the final part size IF there are no buttons with negative coordinates AND + // there are no buttons that is positioned in a coordinate close to the edge enough for not + // fitting (considering their width/height) + int bgWidth = layoutFile.getBackgroundWidth(partName, skinFilesPath); + int bgHeight = layoutFile.getBackgroundHeight(partName, skinFilesPath); + Point bgPos = layoutFile.getBackgroundPosition(partName); + int maxX = partOffset.x + bgPos.x + bgWidth; + int maxY = partOffset.y + bgPos.y + bgHeight; + + // Iterates on the buttons, looking for the area required by each of them + Collection<String> buttonNames = layoutFile.getButtonNames(partName); + + for (String buttonName : buttonNames) + { + int btWidth = layoutFile.getButtonWidth(partName, buttonName, skinFilesPath); + int btHeight = layoutFile.getButtonHeight(partName, buttonName, skinFilesPath); + Point buttonPos = layoutFile.getButtonPosition(partName, buttonName); + + // If a button is described to be drawn outside the current maximum + // (x,y) position, update the (x,y) to make it fit + // + // -------------- -------------- + // (x,y)| | | | + // --- | | | + // | | button | | (x,y) | + // --- with | | ---- + // | negative | | | | button positioned in coordinates + // | coordinate| | | | inside the part, but with width + // | | | ---- large enough not to fit + // | | | | + // | | | | + // | PART | | PART | + // -------------- -------------- + // + // + maxX = Math.max(maxX, partOffset.x + buttonPos.x + btWidth); + maxY = Math.max(maxY, partOffset.y + buttonPos.y + btHeight); + } + + Point imgSize = new Point(maxX, maxY); + + return imgSize; + } + + /** + * Utility method for loading a image, given the model and part/button + * <br><br> + * @param layoutFile The model that represents the layout file + * @param partName The name of the part to have its background image loaded, or that contains the button + * @param buttonName The name of the button we wish the image loaded, or <code>null</code> if we aim to + * load the part background image + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return An image data object, containing the image pixels + */ + private static ImageData getImageData(LayoutFileModel layoutFile, String partName, + String buttonName, File skinFilesPath) + { + File f; + if (buttonName == null) + { + f = layoutFile.getBackgroundImage(partName, skinFilesPath); + } + else + { + f = new File(skinFilesPath, layoutFile.getButtonImage(partName, buttonName).getName()); + } + + return new ImageData(f.getAbsolutePath()); + } + + /** + * Creates a new expanded part image that contains enough space for drawing all the buttons + * <br><br> + * @param layoutFile The model that represents the layout file + * @param layoutName The name of the layout containing the part, if there is one, or <code>null</code> + * @param partName The part to be expanded + * @param offset The part offset to use when positioning the part image at the expanded image + * @param skinFilesPath The path to the skin dir, where skin files can be found + * <br><br> + * @return An image data containing the part image with more space at the background area + */ + private static ImageData generateExpandedPartImageData(LayoutFileModel layoutFile, + String layoutName, String partName, Point offset, File skinFilesPath, Point partOffset, + Point partSize) + { + ImageData img = null; + if (partName != null) + { + // Creates an image data with dimensions defined by partSize, and background color, depth + // and palette defined by bgImg + ImageData bgImg = getImageData(layoutFile, partName, null, skinFilesPath); + img = + generateImageDataWithBackground(layoutFile, layoutName, partSize.x, partSize.y, + bgImg, skinFilesPath); + + // Merges the bgImg pixels at the image data + int[] row = new int[bgImg.width]; + for (int i = 0; i < bgImg.height; i++) + { + bgImg.getPixels(0, i, bgImg.width, row, 0); + img.setPixels(offset.x, offset.y + i, bgImg.width, row, 0); + } + + } + + return img; + } + + /** + * Creates an image data of dimensions x, y and the same background color as srcImg + * <br><br> + * @param layoutFile The model that represents the layout file + * @param layoutName The name of the layout that may have a background color defined + * @param width The width of the image + * @param height The height of the image + * @param srcImg An image that we wish to have its depth, palette (and perhaps background color) + * copied to the new image + * <br><br> + * @return An image of size (width, height), same depth and palette of srcImg and with filled with the + * background color defined by the model or srcImg + */ + private static ImageData generateImageDataWithBackground(LayoutFileModel layoutFile, + String layoutName, int width, int height, ImageData srcImg, File skinFilesPath) + { + ImageData img = new ImageData(width, height, srcImg.depth, srcImg.palette); + + // Discover what is the background color. This is needed to fill the + // spaces around the layout image + RGB bgColor = layoutFile.getLayoutColor(layoutName, skinFilesPath); + int bgPixel = srcImg.palette.getPixel(bgColor); + + // Set the background color to the entire layout image + for (int i = 0; i < img.width; i++) + { + for (int j = 0; j < img.height; j++) + { + img.setPixel(i, j, bgPixel); + } + } + + return img; + } + + /** + * Creates a new image, based on imageToRotate, rotated according to rotation + * <br><br> + * @param imageToRotate The image that is used as source for rotation + * @param rotation (rotation * 90) results in how many degrees to rotate CLOCKWISE + * <br><br> + * @return The rotated image + */ + private static ImageData generateRotatedImage(ImageData imageToRotate, int rotation) + { + // For each rotation case, generates an appropriate image data (with dimensions w/h or h/w + // depending on whether it is landscape or portrait) and copies the imageToRotate pixels at the + // appropriate positions + ImageData rotated; + switch (rotation % 4) + { + case 1: + // 0------- 0 j h + // | --- | --------- + // j| | | | | --- | + // | --- | | | | | + // | | | --- | + // h------- --------- + rotated = + new ImageData(imageToRotate.height, imageToRotate.width, + imageToRotate.depth, imageToRotate.palette); + for (int i = 0; i < imageToRotate.width; i++) + { + for (int j = 0; j < imageToRotate.height; j++) + { + rotated.setPixel(imageToRotate.height - j - 1, i, + imageToRotate.getPixel(i, j)); + } + } + break; + case 2: + // 0 i w 0 i w + // 0------- 0------- + // | --- | | | + // j| | | | j| --- | + // | --- | | | | | + // | | | --- | + // h------- h------- + rotated = + new ImageData(imageToRotate.width, imageToRotate.height, + imageToRotate.depth, imageToRotate.palette); + for (int i = 0; i < imageToRotate.width; i++) + { + for (int j = 0; j < imageToRotate.height; j++) + { + rotated.setPixel(imageToRotate.width - i - 1, imageToRotate.height - j - 1, + imageToRotate.getPixel(i, j)); + } + } + break; + case 3: + // 0 i w + // 0------- 0 j h + // | --- | 0--------- + // j| | | | | --- | + // | --- | i| | | | + // | | | --- | + // h------- w--------- + rotated = + new ImageData(imageToRotate.height, imageToRotate.width, + imageToRotate.depth, imageToRotate.palette); + for (int i = 0; i < imageToRotate.width; i++) + { + for (int j = 0; j < imageToRotate.height; j++) + { + rotated.setPixel(j, imageToRotate.width - i - 1, + imageToRotate.getPixel(i, j)); + } + } + break; + default: + // If 0, there is no need to rotate + rotated = imageToRotate; + break; + } + + return rotated; + } + + /** + * Creates an image that contains buttons with proper transparency. The transparency is defined by + * the isEnter parameter + * + * @param layoutFile The model that represents the layout file + * @param baseImage The image to use as base for generation. Must be a copy, because it will be + * changed in-place. + * @param partName The name of the part where to look for buttons in the model + * @param skinFilesPath The path to the skin dir, where skin files can be found + * @param partOffset What is the calculated offset for the given part + * @param isEnter Whether the image being created will be used for enter or pressed + * @return + */ + private static ImageData generateMergedWithButtonsImage(LayoutFileModel layoutFile, + ImageData baseImage, String partName, File skinFilesPath, Point partOffset, + boolean isEnter) + { + // Iterate on the buttons, merging the buttons pixels to the base image + Collection<String> buttonNames = layoutFile.getButtonNames(partName); + for (String buttonName : buttonNames) + { + ImageData buttonID = getImageData(layoutFile, partName, buttonName, skinFilesPath); + Point buttonPos = layoutFile.getButtonPosition(partName, buttonName); + buttonPos.x += partOffset.x; + buttonPos.y += partOffset.y; + mergeButtonData(baseImage, buttonID, buttonPos, isEnter); + } + + return baseImage; + } + + /** + * Merges the button data to the base image + * <br><br> + * @param baseImage The image that will be modified + * @param buttonImage The image that have the source pixels for the merge operation + * @param buttonPos Where the button is located + * @param isEnter Whether the image being created will be used for enter or pressed + */ + private static void mergeButtonData(ImageData baseImage, ImageData buttonImage, + Point buttonPos, boolean isEnter) + { + // Pixel/alpha buffers + int[] baseImgPixels = new int[buttonImage.width]; + int[] buttonImgPixels = new int[buttonImage.width]; + byte[] buttonAlphas = new byte[buttonImage.width]; + int[] intButtonAlphas = new int[buttonImage.width]; + + // For each pixel row, get the button pixel data, apply the transparency + // defined by alpha and copy data to the base image + for (int i = 0; i < buttonImage.height; i++) + { + baseImage.getPixels(buttonPos.x, buttonPos.y + i, buttonImage.width, baseImgPixels, 0); + buttonImage.getPixels(0, i, buttonImage.width, buttonImgPixels, 0); + buttonImage.getAlphas(0, i, buttonImage.width, buttonAlphas, 0); + + for (int j = 0; j < buttonAlphas.length; j++) + { + // As buttonAlphas is a signed byte array with range -127 to 128, and alpha is + // an integer in the range 0 to 255, overflows can happen. This calculation assures + // that the alpha variable has correct value in an integer array. + intButtonAlphas[j] = + (buttonAlphas[j] >= 0 ? buttonAlphas[j] + : ((buttonAlphas[j]) & ((byte) 0x7F)) + 128); + } + + if (!isEnter) + { + for (int j = 0; j < buttonAlphas.length; j++) + { + if (intButtonAlphas[j] > 0) + { + intButtonAlphas[j] += (255 - intButtonAlphas[j]) / 4; + } + } + } + + addTransparency(baseImgPixels, buttonImgPixels, intButtonAlphas, baseImage.palette, + buttonImage.palette); + + baseImage.setPixels(buttonPos.x, buttonPos.y + i, buttonImage.width, baseImgPixels, 0); + } + } + + /** + * Calculates transparency for the button pixels and sets them to the base + * pixels buffer + * <br><br> + * @param basePixels The buffer containing pixels for a given line of the base image + * @param buttonPixels The buffer containing pixels for a given line of the button image + * @param buttonAlphas The buffer containing alpha information for a given line of the button image + * @param basePalette The color palette used by the base image + * @param buttonPalette The color palette used by the button image + */ + private static void addTransparency(int[] basePixels, int[] buttonPixels, int[] buttonAlphas, + PaletteData basePalette, PaletteData buttonPalette) + { + for (int i = 0; i < buttonPixels.length; i++) + { + RGB buttonRgb = buttonPalette.getRGB(buttonPixels[i]); + RGB baseRgb = basePalette.getRGB(basePixels[i]); + + RGB newRgb = + new RGB(calculateMerge(baseRgb.red, buttonRgb.red, buttonAlphas[i]), + calculateMerge(baseRgb.green, buttonRgb.green, buttonAlphas[i]), + calculateMerge(baseRgb.blue, buttonRgb.blue, buttonAlphas[i])); + + basePixels[i] = basePalette.getPixel(newRgb); + } + } + + /** + * Calculates the transparency for a single color component + * <br><br> + * @param background The background color component + * @param foreground The foreground color component + * @param alpha The alpha to be applied. 0 means pure transparent + * (background color is used). 255 means pure opaque (foreground color is used) + * <br><br> + * @return The resulting color + */ + private static int calculateMerge(int background, int foreground, int alpha) + { + // weighted medium of foreground color and background color, with alpha as parameter + return (foreground * alpha + background * (255 - alpha)) / 255; + } + + /** + * Translates the part position information from the Google format to the upper-left reference used + * by the viewer + * + * @param layoutFile The model that represents the layout file + * @param layoutName The layout where the part is included + * @param partName The part to have its position calculated + * @param skinFilesPath The path to the skin dir, where skin files can be found + * + * @return The point where the part must be drawn in the layout, using as reference the upper-left + * corner of the layout image + */ + private static Point translatePartPosition(LayoutFileModel layoutFile, String layoutName, + String partName, File skinFilesPath, Point partOffset, Point partSize) + { + // Collect needed data + int rotation = layoutFile.getPartRotationAtLayout(layoutName, partName); + Point partPos = layoutFile.getPartPositionAtLayout(layoutName, partName, skinFilesPath); + int bgWidth; + int bgHeight; + if (layoutFile.partHasBg(partName)) + { + bgWidth = layoutFile.getBackgroundWidth(partName, skinFilesPath); + bgHeight = layoutFile.getBackgroundHeight(partName, skinFilesPath); + } + else + { + bgWidth = layoutFile.getBackgroundWidth(layoutName, skinFilesPath); + bgHeight = layoutFile.getBackgroundHeight(layoutName, skinFilesPath); + } + int extraOnEndW = partSize.x - bgWidth - partOffset.x; + int extraOnEndH = partSize.y - bgHeight - partOffset.y; + + // Calculate translation + switch (rotation % 4) + { + case 1: + // Landscape, top of part image is at the right (90 degrees clockwise rotation) + // The point we must return is the one at the bottom-left corner of the part, considering + // offset and extra space in the end of the part image (which was added so that buttons + // at the right side of the part fit) + // + // BEFORE AFTER + // (0,0) (0,0) + // --------- --------- + // | --- | | --- | + // | | | | | | | | + // | --- | | --- | + // --------- --------- + partPos.x = partPos.x - partOffset.y - bgHeight; + partPos.y = partPos.y - extraOnEndW; + break; + case 2: + // Portrait, top of part image is at the bottom (180 degrees clockwise rotation) + // The point we must return is the one at the bottom-right corner of the part + // + // BEFORE AFTER + // (0,0) + // ------- ------- + // | | | | + // | --- | | --- | + // | | | | | | | | + // | --- | | --- | + // ------- ------- + // (0,0) + partPos.x = partPos.x - bgWidth; + partPos.y = partPos.y - bgHeight; + break; + case 3: + // Landscape, top of part image is at the left (270 degrees clockwise rotation) + // The point we must return is the one at the top-right corner of the part, considering + // offset and extra space in the end of the part image (which was added so that buttons + // at the right side of the part fit) + // + // BEFORE AFTER + // (0,0) + // --------- --------- + // | --- | | --- | + // | | | | | | | | + // | --- | | --- | + // --------- --------- + //(0,0) + partPos.x = partPos.x - extraOnEndH; + partPos.y = partPos.y - partOffset.x - bgWidth; + break; + default: + // No translation is needed when there is no rotation + break; + } + + return partPos; + } + + /** + * Translates the button position information from the Google format to the upper-left reference used + * by the viewer + * + * @param layoutFile The model that represents the layout file + * @param layoutName The layout where the part is included, or <code>null</code> if the skin does + * not support layout + * @param partName The part where the button is included + * @param buttonName The button to have its position calculated + * @param skinFilesPath The path to the skin dir, where skin files can be found + * + * @return The point where the button must be drawn in the part, using as reference the upper-left + * corner of the part image + */ + private static Point translateButtonPosition(LayoutFileModel layoutFile, String layoutName, + String partName, String buttonName, File skinFilesPath, Point partOffset, Point partSize) + { + // Collect button data + Point buttonPos = layoutFile.getButtonPosition(partName, buttonName); + int buttonW = layoutFile.getButtonWidth(partName, buttonName, skinFilesPath); + int buttonH = layoutFile.getButtonHeight(partName, buttonName, skinFilesPath); + + // Update the button position, considering part offset/size and rotation + buttonPos = + translatePartElementPosition(layoutFile, layoutName, partName, skinFilesPath, + buttonPos, buttonW, buttonH, partOffset, partSize); + + return buttonPos; + } + + /** + * Translates a part element position (display/buttons) from the Google format to the upper-left + * reference used by the viewer + * + * @param layoutFile The model that represents the layout file + * @param layoutName The layout where the part is included, or <code>null</code> if the skin does + * not support layout + * @param partName The part where the element is included + * @param skinFilesPath The path to the skin dir, where skin files can be found + * @param partElementPos The position of the part element as described by layoutFile + * @param partElementWidth The width of the part element as described by layoutFile + * @param partElementHeight The height of the part element as described by layoutFile + * + * @return The point where the element must be drawn in the part, using as reference the upper-left + * corner of the part image + */ + private static Point translatePartElementPosition(LayoutFileModel layoutFile, + String layoutName, String partName, File skinFilesPath, Point partElementPos, + int partElementWidth, int partElementHeight, Point partOffset, Point partSize) + { + Point translated = new Point(0, 0); + int rotation = layoutFile.getPartRotationAtLayout(layoutName, partName); + + // Due to rotation, the part position will be referenced to a non-appropriate image corner. + // The following operation guarantees that the part position is still at the upper left corner + // even after rotation. + Point partPos = + translatePartPosition(layoutFile, layoutName, partName, skinFilesPath, partOffset, + partSize); + + // Calculate position. + // + // OBS: Every time we need the part size for our the calculation, we must subtract the part offset + // as well. This is because during part size calculation, we have already summed the offset and we + // need to rework the offset due to rotation (i.e., sometimes we need to sum offset.y instead of + // offset.x due to rotation, and vice-versa). This is being illustrated at the lines below with + // parenthesis. + switch (rotation % 4) + { + case 1: + // BEFORE AFTER + //(0,0) + // --------- (0,0) + // | | ----------- + // |(x,y) | (x,y) | + // | --- | | --- | + // | | | | | | | | + // | --- | | --- | + // --------- ----------- + translated.x = + partPos.x - partOffset.x + (partSize.y - partOffset.y) - partElementPos.y + - partElementHeight; + translated.y = partPos.y - partOffset.y + partOffset.x + partElementPos.x; + break; + case 2: + // BEFORE AFTER + //(0,0) (0,0) + // --------- --------- + // | | (x,y) --- | + // |(x,y) | | | | | + // | --- | | --- | + // | | | | | | + // | --- | | | + // --------- --------- + translated.x = + partPos.x + (partSize.x - partOffset.x) - partOffset.x - partElementPos.x + - partElementWidth; + translated.y = + partPos.y + (partSize.y - partOffset.y) - partOffset.y - partElementPos.y + - partElementHeight; + break; + case 3: + // BEFORE AFTER + //(0,0) + // --------- (0,0) + // | | ----------- + // |(x,y) | |(x,y)--- | + // | --- | | | | | + // | | | | | --- | + // | --- | | | + // --------- ----------- + translated.x = partPos.x - partOffset.x + partOffset.y + partElementPos.y; + translated.y = + partPos.y - partOffset.y + (partSize.x - partOffset.x) - partElementPos.x + - partElementWidth; + break; + default: + translated.x = partElementPos.x + partPos.x; + translated.y = partElementPos.y + partPos.y; + break; + } + + return translated; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ILayoutBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ILayoutBean.java new file mode 100644 index 0000000..70e9510 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ILayoutBean.java @@ -0,0 +1,27 @@ +/* +* 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.skin.android.parser; + +public interface ILayoutBean +{ + /** + * Sets the key/value pair at the bean, according to the rules defined by each bean + * + * @param key The key that represents the item to be set + * @param value The value to be set to the item represented by key + */ + void setKeyValue(String key, String value); +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ILayoutConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ILayoutConstants.java new file mode 100644 index 0000000..98f71f3 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ILayoutConstants.java @@ -0,0 +1,78 @@ +/* +* 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.skin.android.parser; + +/** + * DESCRIPTION: + * This class lists all default constants contained into a layout file + * + * RESPONSIBILITY: + * Support on parsing of layout files + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by the LayoutFileParser class only + */ +interface ILayoutConstants +{ + String OPEN_BRACKET = "{"; + + String CLOSE_BRACKET = "}"; + + String MAIN_LEVEL_PARTS = "parts"; + + String MAIN_LEVEL_LAYOUTS = "layouts"; + + String MAIN_LEVEL_KEYBOARD = "keyboard"; + + String MAIN_LEVEL_NETWORK = "network"; + + String MAIN_LEVEL_BACKGROUND = "background"; + + String MAIN_LEVEL_DISPLAY = "display"; + + String MAIN_LEVEL_BUTTON = "button"; + + String PART_BUTTONS = "buttons"; + + String KEYBOARD_CHARMAP = "charmap"; + + String NETWORK_SPEED = "speed"; + + String NETWORK_DELAY = "delay"; + + String ATTR_WIDTH = "width"; + + String ATTR_HEIGHT = "height"; + + String ATTR_X = "x"; + + String ATTR_Y = "y"; + + String ATTR_IMAGE = "image"; + + String ATTR_NAME = "name"; + + String LAYOUT_COLOR = "color"; + + String LAYOUT_EVENT = "event"; + + String DPAD_ROTATION = "dpad-rotation"; + + String PARTREF_ROTATION = "rotation"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ImagePositionBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ImagePositionBean.java new file mode 100644 index 0000000..d8a5c12 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/ImagePositionBean.java @@ -0,0 +1,175 @@ +/* +* 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.skin.android.parser; + +import java.io.File; + +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.ui.PlatformUI; + +/** + * DESCRIPTION: + * This class represents a node containing image, x and y data + * + * RESPONSIBILITY: + * Represent image nodes of the layout file + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by the LayoutFileParser and LayoutFileModel classes only + */ +public class ImagePositionBean implements ILayoutConstants, ILayoutBean +{ + /** + * Name of the node + */ + private String name; + + /** + * Location of the file that contains the image + */ + private File imageLocation; + + /** + * X position where to draw the image + */ + private String xPos; + + /** + * Y position where to draw the image + */ + private String yPos; + + private int width = -1; + + private int height = -1; + + /** + * Constructor + * Creates the node and assign a name to it + */ + ImagePositionBean(String name) + { + this.name = name; + } + + /** + * Retrieves the image location + * + * @return The image location + */ + File getImageLocation() + { + return imageLocation; + } + + /** + * Retrieves the X position where to draw the image + * + * @return The image X position + */ + String getXPos() + { + return xPos; + } + + /** + * Retrieves the Y position where to draw the image + * + * @return The image Y position + */ + String getYPos() + { + return yPos; + } + + int getWidth(File skinFilesPath) + { + if (width == -1) + { + populateWidthHeight(skinFilesPath); + } + + return width; + } + + int getHeight(File skinFilesPath) + { + if (width == -1) + { + populateWidthHeight(skinFilesPath); + } + + return height; + } + + /** + * Retrieves the name of this node + * + * @return The node name + */ + String getName() + { + return name; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "ImagePosition: " + name; + } + + private void populateWidthHeight(final File skinFilesPath) + { + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + ImageData id = + new ImageData(new File(skinFilesPath, imageLocation.getName()) + .getAbsolutePath()); + width = id.width; + height = id.height; + } + }); + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.skin.android.parser.ILayoutBean#setKeyValue(java.lang.String, java.lang.String) + */ + public void setKeyValue(String key, String value) + { + if (ATTR_IMAGE.equals(key)) + { + imageLocation = new File(value); + } + else if (ATTR_X.equals(key)) + { + xPos = value; + } + else if (ATTR_Y.equals(key)) + { + yPos = value; + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutBean.java new file mode 100644 index 0000000..586f10e --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutBean.java @@ -0,0 +1,231 @@ +/* +* 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.skin.android.parser; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import org.eclipse.swt.graphics.RGB; + +/** + * DESCRIPTION: + * This class represents a layout structure from the layout file + * + * RESPONSIBILITY: + * Represent layout structures + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by the LayoutFileParser and LayoutFileModel classes only + */ +public class LayoutBean implements ILayoutConstants, ILayoutBean +{ + /** + * Default name of a landscape layout (used when creating pseudo-layouts) + * Pseudo-layouts are created when the skin has only one part and no layouts + */ + public static final String DEFAULT_LAYOUT_NAME = "default"; + + /** + * Default name of a portrait layout (used when creating pseudo-layouts) + * Pseudo-layouts are created when the skin has only one part and no layouts + */ + public static final String ROTATED_LAYOUT_NAME = "rotated"; + + /** + * The layout structure name + */ + private String name; + + /** + * The layout width + */ + private String width; + + /** + * The layout height + */ + private String height; + + /** + * Thw layout color + */ + private RGB color; + + /** + * The event command used to switch to this layout + */ + private String event; + + /** + * Dpad rotation, steps of 90° + */ + private int dpadRotation; + + /** + * The collection of parts that integrate this layout + */ + private Collection<PartRefBean> parts = new LinkedHashSet<PartRefBean>(); + + /** + * Constructor + * Builds a new layout structure with the given name + * + * @param name The layout name + */ + LayoutBean(String name) + { + this.name = name; + } + + /** + * Creates a new reference to a part, registers it and returns it to the user + * + * @param refName The name to assign to the part reference + * + * @return The part reference + */ + PartRefBean newPartRef(String refName) + { + PartRefBean bean = new PartRefBean(refName); + parts.add(bean); + return bean; + } + + /** + * Retrieves the layout width + * + * @return The layout width + */ + String getWidth() + { + return width; + } + + /** + * Retrieves the layout height + * + * @return The layout height + */ + String getHeight() + { + return height; + } + + /** + * Retrieves the layout color + * + * @return The layout color + */ + RGB getColor() + { + return color; + } + + /** + * Retrieves the event to switch to this layout + * + * @param event The event + */ + String getEvent() + { + return event; + } + + /** + * @return the dpadRotation + */ + int getDpadRotation() + { + return dpadRotation; + } + + /** + * Retrieves this layout name + * + * @return This layout name + */ + String getName() + { + return name; + } + + /** + * Retrieves all references to parts from this layout + * + * @return A collection containing part references + */ + Collection<PartRefBean> getPartRefs() + { + return parts; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "Layout: " + name; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.skin.android.parser.ILayoutBean#setKeyValue(java.lang.String, java.lang.String) + */ + public void setKeyValue(String key, String value) + { + if (ATTR_WIDTH.equals(key)) + { + width = value; + } + else if (ATTR_HEIGHT.equals(key)) + { + height = value; + } + else if (LAYOUT_COLOR.equals(key)) + { + Integer colorInt = Integer.decode(value); + int blue = colorInt.intValue() & 0xFF; + int green = (colorInt.intValue() & 0xFF00) >> 8; + int red = (colorInt.intValue() & 0xFF0000) >> 16; + RGB rgb = new RGB(red, green, blue); + + color = rgb; + } + else if (LAYOUT_EVENT.equals(key)) + { + event = value; + } + else if (DPAD_ROTATION.equals(key)) + { + int intValue; + try + { + intValue = Integer.parseInt(value); + } + catch (NumberFormatException e) + { + //Assume there's no rotation + intValue = 0; + } + dpadRotation = intValue; + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileModel.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileModel.java new file mode 100644 index 0000000..922b396 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileModel.java @@ -0,0 +1,706 @@ +/* +* 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.skin.android.parser; + +import java.io.File; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; + +/** + * DESCRIPTION: + * This class represents a skin layout file + * + * RESPONSIBILITY: + * Represent all the contents of a skin layout file + * + * COLABORATORS: + * None. + * + * USAGE: + * Use the class public APIs to retrieve data of the skin + */ +public class LayoutFileModel +{ + /** + * Event to rotate the screen + */ + private static final String ROTATE_SCREEN_EVENT = "EV_SW:0:0"; + + /** + * Command to rotate the screen + */ + private static final String ROTATE_SCREEN_CMD = "5 0 0"; + + /** + * Command to get the screen back to its default orientation + */ + private static final String RETURN_TO_DEFAULT_SCREEN_CMD = "5 0 1"; + + /** + * A collection containing all layouts read from the layout file + */ + private Collection<LayoutBean> layouts; + + /** + * A collection containing all parts read from the layout file + */ + private Collection<PartBean> parts = new LinkedHashSet<PartBean>(); + + /** + * The keyboard charmap used by this skin + */ + private String keyboardCharmap; + + /** + * The default network speed used by this skin + */ + private String networkSpeed; + + /** + * The default network delay used by this skin + */ + private String networkDelay; + + /** + * Sets the keyboard charmap used by this skin + * + * @param keyboardCharmap The keyboard charmap name + */ + void setKeyboardCharmap(String keyboardCharmap) + { + this.keyboardCharmap = keyboardCharmap; + } + + /** + * Sets the default network speed used by this skin + * + * @param keyboardCharmap The default network speed used by this skin + */ + void setNetworkSpeed(String networkSpeed) + { + this.networkSpeed = networkSpeed; + } + + /** + * Sets the default network delay of this skin + * + * @param The default network delay + */ + void setNetworkDelay(String networkDelay) + { + this.networkDelay = networkDelay; + } + + /** + * Creates a new part, registers it and returns it to the user + * + * This version is used when the skin is simple, i.e. when there is a single + * part and no layouts defined. To support landscape/portrait rotation, we create + * "pseudo-layouts" at memory as well. + * + * @return The part + */ + PartBean newPart() + { + return newPart(PartBean.UNIQUE_PART); + } + + /** + * Creates a new part, registers it and returns it to the user + * Use this version when the skin have multiple parts + * (i.e., if there is a "parts" element in the layout file) + * + * @param name The part name + * + * @return The part + */ + PartBean newPart(String name) + { + PartBean bean = new PartBean(name); + parts.add(bean); + return bean; + } + + /** + * Creates a new layout, registers it and returns it to the user + * + * @param name The layout name + * + * @return The layout + */ + LayoutBean newLayout(String name) + { + LayoutBean bean = new LayoutBean(name); + if (layouts == null) + { + layouts = new LinkedHashSet<LayoutBean>(); + } + layouts.add(bean); + + return bean; + } + + /** + * Retrieves the keyboard charmap used by this skin + * + * @return The keyboard charmap name + */ + public String getKeyboardCharmap() + { + return keyboardCharmap; + } + + /** + * Retrieves the default network speed of this skin + * + * @return The default network speed + */ + public String getNetworkSpeed() + { + return networkSpeed; + } + + /** + * Retrieves the default network delay of this skin + * + * @return The default network delay + */ + public String getNetworkDelay() + { + return networkDelay; + } + + public List<String> getLayoutNames() + { + List<String> layoutNames = new LinkedList<String>(); + if (layouts != null) + { + for (LayoutBean bean : layouts) + { + layoutNames.add(bean.getName()); + } + } + + return layoutNames; + } + + public Collection<String> getPartNames() + { + Collection<String> partNames = new LinkedHashSet<String>(); + for (PartBean bean : parts) + { + partNames.add(bean.getName()); + } + + return partNames; + } + + public Collection<String> getLayoutPartNames(String layoutName) + { + Collection<String> layoutPartNames = new LinkedHashSet<String>(); + LayoutBean bean = getLayoutByName(layoutName); + if (bean != null) + { + Collection<PartRefBean> partRefs = bean.getPartRefs(); + if (partRefs != null) + { + for (PartRefBean aRef : partRefs) + { + layoutPartNames.add(aRef.getPartName()); + } + } + } + + return layoutPartNames; + } + + public Point getPartPositionAtLayout(String layoutName, String partName, File skinFilesPath) + { + Point partPosition = null; + + LayoutBean bean = getLayoutByName(layoutName); + if (bean != null) + { + Collection<PartRefBean> partRefs = bean.getPartRefs(); + for (PartRefBean prBean : partRefs) + { + if (prBean.getPartName().equals(partName)) + { + int x = Integer.parseInt(prBean.getX()); + String yStr = prBean.getY(); + int y; + if (yStr == null) + { + y = getBackgroundWidth(partName, skinFilesPath); + } + else + { + y = Integer.parseInt(yStr); + } + + partPosition = new Point(x, y); + break; + } + } + } + else + { + partPosition = new Point(0, 0); + } + + return partPosition; + } + + public int getPartRotationAtLayout(String layoutName, String partName) + { + int partRotation = 0; + + LayoutBean bean = getLayoutByName(layoutName); + if (bean != null) + { + Collection<PartRefBean> partRefs = bean.getPartRefs(); + for (PartRefBean prBean : partRefs) + { + if (prBean.getPartName().equals(partName)) + { + String rotStr = prBean.getRotation(); + if (rotStr != null) + { + partRotation = Integer.parseInt(rotStr); + } + break; + } + } + } + + return partRotation; + } + + public int getDpadRotation(String layoutName) + { + int dPadRotation = 0; + LayoutBean bean = getLayoutByName(layoutName); + if (bean != null) + { + dPadRotation = bean.getDpadRotation(); + } + + return dPadRotation; + } + + public Collection<String> getButtonNames(String partName) + { + Collection<String> buttonNames = new LinkedHashSet<String>(); + + PartBean bean = getPartByName(partName); + if (bean != null) + { + Collection<ImagePositionBean> buttons = bean.getButtons(); + if (buttons != null) + { + for (ImagePositionBean button : buttons) + { + buttonNames.add(button.getName()); + } + } + } + return buttonNames; + } + + public int getLayoutWidth(String layoutName) + { + int width = 0; + LayoutBean layout = getLayoutByName(layoutName); + if (layout != null) + { + width = Integer.parseInt(layout.getWidth()); + } + + return width; + } + + public int getLayoutHeight(String layoutName) + { + int height = 0; + LayoutBean layout = getLayoutByName(layoutName); + if (layout != null) + { + height = Integer.parseInt(layout.getHeight()); + } + + return height; + } + + public RGB getLayoutColor(String layoutName, File skinFilesPath) + { + RGB color = null; + LayoutBean layout = getLayoutByName(layoutName); + if (layout != null) + { + color = layout.getColor(); + if (color == null) + { + String mainPart = getMainPartName(layoutName); + File image = getBackgroundImage(mainPart, skinFilesPath); + ImageData img = new ImageData(image.getAbsolutePath()); + int pixel = img.getPixel(0, 0); + color = img.palette.getRGB(pixel); + layout.setKeyValue(ILayoutConstants.LAYOUT_COLOR, + "0x" + Integer.toHexString(color.red) + Integer.toHexString(color.green) + + Integer.toHexString(color.blue)); + } + } + else + { + color = new RGB(255, 255, 255); + } + + return color; + } + + public String getLayoutEvent(String layoutName) + { + String event = ""; + LayoutBean layout = getLayoutByName(layoutName); + if (layout != null) + { + event = layout.getEvent(); + } + + return event; + } + + public String getLayoutSwitchCommand(String layoutName) + { + LayoutBean bean = getLayoutByName(layoutName); + String event = bean.getEvent(); + if (ROTATE_SCREEN_EVENT.equals(event)) + { + return ROTATE_SCREEN_CMD; + } + else + { + return RETURN_TO_DEFAULT_SCREEN_CMD; + } + } + + public boolean isSwapWidthHeightNeededAtLayout(String layoutName) + { + return isSwapWidthHeightNeededAtLayout(layoutName, getMainPartName(layoutName)); + } + + public boolean isSwapWidthHeightNeededAtLayout(String layoutName, String partName) + { + boolean isRotated = false; + if (partName != null) + { + int rotation = getPartRotationAtLayout(layoutName, partName); + isRotated = rotation % 2 != 0; + } + return isRotated; + } + + public File getBackgroundImage(String partName, File skinFilesPath) + { + File backgroundFile = null; + PartBean part = getPartByName(partName); + if (part != null) + { + ImagePositionBean backgroundBean = part.getBackground(); + if (backgroundBean != null) + { + backgroundFile = + new File(skinFilesPath, backgroundBean.getImageLocation().getName()); + } + } + + return backgroundFile; + } + + public Point getBackgroundPosition(String partName) + { + Point bgPosition = null; + PartBean part = getPartByName(partName); + if (part != null) + { + ImagePositionBean bgBean = part.getBackground(); + String xStr = null; + String yStr = null; + if (bgBean != null) + { + + xStr = bgBean.getXPos(); + yStr = bgBean.getYPos(); + } + + if ((xStr != null) && (yStr != null)) + { + bgPosition = new Point(Integer.parseInt(xStr), Integer.parseInt(yStr)); + } + else + { + bgPosition = new Point(0, 0); + } + } + + return bgPosition; + } + + public int getBackgroundWidth(String partName, File skinFilesPath) + { + int width = -1; + PartBean part = getPartByName(partName); + if (part != null) + { + ImagePositionBean bgBean = part.getBackground(); + if (bgBean != null) + { + width = bgBean.getWidth(skinFilesPath); + } + } + + return width; + } + + public int getBackgroundHeight(String partName, File skinFilesPath) + { + int height = -1; + PartBean part = getPartByName(partName); + if (part != null) + { + ImagePositionBean bgBean = part.getBackground(); + if (bgBean != null) + { + height = bgBean.getHeight(skinFilesPath); + } + } + + return height; + } + + public File getButtonImage(String partName, String buttonName) + { + File buttonFile = null; + ImagePositionBean button = getButtonByName(partName, buttonName); + if (button != null) + { + buttonFile = button.getImageLocation(); + } + + return buttonFile; + } + + public Point getButtonPosition(String partName, String buttonName) + { + Point buttonPos = null; + ImagePositionBean button = getButtonByName(partName, buttonName); + if (button != null) + { + buttonPos = + new Point(Integer.parseInt(button.getXPos()), + Integer.parseInt(button.getYPos())); + } + + return buttonPos; + } + + public int getButtonWidth(String partName, String buttonName, File skinFilesPath) + { + int width = -1; + ImagePositionBean button = getButtonByName(partName, buttonName); + if (button != null) + { + width = button.getWidth(skinFilesPath); + } + + return width; + } + + public int getButtonHeight(String partName, String buttonName, File skinFilesPath) + { + int height = -1; + ImagePositionBean button = getButtonByName(partName, buttonName); + if (button != null) + { + height = button.getHeight(skinFilesPath); + } + + return height; + } + + public Point getDisplayPosition(String partName) + { + Point displayPos = null; + + PartBean bean = getPartByName(partName); + if (bean != null) + { + RectangleBean dispBean = bean.getDisplay(); + displayPos = + new Point(Integer.parseInt(dispBean.getXPos()), Integer.parseInt(dispBean + .getYPos())); + } + + return displayPos; + } + + public int getDisplayWidth(String partName) + { + int width = -1; + + PartBean bean = getPartByName(partName); + if (bean != null) + { + RectangleBean dispBean = bean.getDisplay(); + width = Integer.parseInt(dispBean.getWidth()); + } + + return width; + } + + public int getDisplayHeight(String partName) + { + int height = -1; + + PartBean bean = getPartByName(partName); + if (bean != null) + { + RectangleBean dispBean = bean.getDisplay(); + height = Integer.parseInt(dispBean.getHeight()); + } + + return height; + } + + private LayoutBean getLayoutByName(String layoutName) + { + LayoutBean layoutToReturn = null; + + if (layouts != null) + { + Iterator<LayoutBean> it = layouts.iterator(); + while (it.hasNext()) + { + LayoutBean bean = it.next(); + if (bean.getName().equals(layoutName)) + { + layoutToReturn = bean; + break; + } + } + } + + return layoutToReturn; + } + + public PartBean getPartByName(String partName) + { + PartBean partToReturn = null; + + Iterator<PartBean> it = parts.iterator(); + while (it.hasNext()) + { + PartBean bean = it.next(); + if (bean.getName().equals(partName)) + { + partToReturn = bean; + break; + } + } + + return partToReturn; + } + + private ImagePositionBean getButtonByName(String partName, String buttonName) + { + ImagePositionBean buttonToReturn = null; + PartBean pBean = getPartByName(partName); + if (pBean != null) + { + Collection<ImagePositionBean> buttons = pBean.getButtons(); + if (buttons != null) + { + for (ImagePositionBean bBean : buttons) + { + if (bBean.getName().equals(buttonName)) + { + buttonToReturn = bBean; + break; + } + } + } + } + + return buttonToReturn; + } + + /** + * Retrieves a layout main part, i.e. the first part that contains a display + * In future releases, check if it is needed to change this concept of "main part" + * + * @param layoutName The layout which main part is to be discovered + * + * @return The name of the layout main part + */ + public String getMainPartName(String layoutName) + { + String mainPartName = null; + for (LayoutBean layout : layouts) + { + if (layout.getName().equals(layoutName)) + { + Collection<PartRefBean> allRefs = layout.getPartRefs(); + for (PartRefBean partRef : allRefs) + { + String partName = partRef.getPartName(); + for (PartBean aPart : parts) + { + String aPartName = aPart.getName(); + if ((aPartName.equals(partName)) && (aPart.getDisplay() != null)) + { + mainPartName = aPartName; + break; + } + } + if (mainPartName != null) + { + break; + } + } + } + if (mainPartName != null) + { + break; + } + } + + return mainPartName; + } + + public boolean partHasBg(String partName) + { + PartBean part = getPartByName(partName); + ImagePositionBean background = part.getBackground(); + return background != null; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java new file mode 100644 index 0000000..92d7664 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java @@ -0,0 +1,552 @@ +/* +* 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.skin.android.parser; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.URL; +import java.nio.CharBuffer; +import java.util.Collection; +import java.util.EmptyStackException; +import java.util.Stack; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; + +/** + * DESCRIPTION: + * This class parses a layout file into a LayoutFileModel object + * + * RESPONSIBILITY: + * Parse the layout file + * + * COLABORATORS: + * None. + * + * USAGE: + * Call readLayout method passing a file to be parsed and retrieve the + * correspondent LayoutFileModel object + */ +public class LayoutFileParser implements ILayoutConstants +{ + /** + * Name of the layout descriptor file at the skin folder + */ + private static final String LAYOUT_FILE_NAME = "layout"; + + /** + * Name of the pseudo layout descriptor file at the res folder + */ + private static final String PSEUDO_LAYOUT_FILE = "res/pseudolayout"; + + /** + * The pattern used for generating tokens out of the layout file + */ + private static final String SPLIT_PATTERN = "[\n\r \t]+"; + + /** + * Parses a layout file + * + * @param skinFilesPath The path to the skin folder + * + * @return a model containing all data read from the layout file + * + * @throws SkinException If it is not possible to read the layout file + */ + public static LayoutFileModel readLayout(File skinFilesPath) throws SkinException + { + LayoutFileModel model = new LayoutFileModel(); + File layoutPath = new File(skinFilesPath, LAYOUT_FILE_NAME); + String fileContents; + if ((layoutPath != null) && (layoutPath.isFile())) + { + fileContents = getLayoutFileContents(layoutPath); + parseLayoutFile(fileContents, model); + } + + Collection<String> partNames = model.getPartNames(); + if ((model.getLayoutNames().size() == 0) && (partNames.size() == 1) + && (partNames.iterator().next().equals(PartBean.UNIQUE_PART))) + { + fileContents = getPseudoLayoutFileContents(); + parseLayoutFile(fileContents, model); + } + + return model; + } + + /** + * Parses the provided layout file contents + * + * @param fileContents All the contents of a layout file + * @param model The model where to set the parsed data + * + * @throws SkinException If the layout file is corrupted, or has erroneous syntax + */ + private static void parseLayoutFile(String fileContents, LayoutFileModel model) + throws SkinException + { + // process given string to remove comments + String cleanContents = ""; + BufferedReader reader = null; + try + { + StringBuffer contentBuffer = new StringBuffer(); + reader = new BufferedReader(new StringReader(fileContents)); + String line = null; + do + { + line = reader.readLine(); + String lineCopy = line; + if ((line != null) && !lineCopy.trim().startsWith("#")) + { + contentBuffer.append(line + '\n'); + } + } + while (line != null); + cleanContents = contentBuffer.toString(); + } + catch (IOException e) + { + //try to continue with the parser + cleanContents = fileContents; + } + finally + { + try + { + reader.close(); + } + catch (IOException e) + { + StudioLogger.error("Could not close input stream: ", e.getMessage()); //$NON-NLS-1$ + } + } + + String[] tokens = cleanContents.split(SPLIT_PATTERN); + + Stack<Object> stack = new Stack<Object>(); + + // At this point, the file has been read into hundreds of token, including blocks, + // "{", "}", keys and values + + String currentTag = null; + String key = null; + + // Iterate on the tokens + try + { + for (String aToken : tokens) + { + // When the token is a "{", that means we need to stack something. This "something" + // will be removed from stack when we find a matching "}" + if (OPEN_BRACKET.equals(aToken)) + { + // Every word is interpreted as a key at first. If we find a "{", it must be + // re-interpreted as a tag instead + if (key != null) + { + currentTag = key; + key = null; + } + addElementsToStack(stack, model, currentTag); + } + // When the token is a "}" we must remove something from the stack + else if (CLOSE_BRACKET.equals(aToken)) + { + removeElementsFromStack(stack); + } + else + { + // A word is interpreted as a key by default. If the key is already set, we will + // have a key-value pair and are able to assign it to something at the model + if (key == null) + { + key = aToken; + } + else + { + setKeyValuePair(stack, model, currentTag, key, aToken); + key = null; + } + } + } + + } + catch (EmptyStackException e) + { + throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_BracketsDoNotMatch); + } + + if (!stack.isEmpty()) + { + // When there is only a part bean at the first level, that means we have finished + // parsing a single part layout. Remove it from the stack as well. + // + // NOTE: when creating the single part layout, we have added this additional element + // to the stack + if ((stack.size() == 1) && (stack.get(0) instanceof PartBean) + && (((PartBean) stack.get(0)).getName().equals(PartBean.UNIQUE_PART))) + { + stack.pop(); + } + else + { + throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_BracketsDoNotMatch); + } + } + + } + + /** + * Reads the contents of the provided file into a String object + * + * @param layoutPath A file pointing to an "layout" file + * + * @return A string with all the contents of the file + * + * @throws SkinException If the file cannot be read + */ + private static String getLayoutFileContents(File layoutPath) throws SkinException + { + int fileSize = (int) layoutPath.length(); + char[] buffer = new char[fileSize]; + + FileReader fr = null; + try + { + fr = new FileReader(layoutPath); + fr.read(buffer); + } + catch (IOException e) + { + error("The file " + layoutPath.getAbsolutePath() + " could not be read. cause=" + + e.getMessage()); + throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_LayoutFileCouldNotBeRead); + } + finally + { + try + { + if (fr != null) + { + fr.close(); + } + } + catch (IOException e) + { + warn("The file " + layoutPath.getAbsolutePath() + + " could not be closed after reading"); + } + } + + return String.copyValueOf(buffer); + } + + /** + * Gets the contents of the pseudo layout file, for merging to the current model + * + * @return A string containing all the contents of the pseudo layout file + * + * @throws SkinException If the file cannot be read + */ + private static String getPseudoLayoutFileContents() throws SkinException + { + URL url = EmulatorPlugin.getDefault().getBundle().getResource(PSEUDO_LAYOUT_FILE); + CharBuffer buffer = CharBuffer.allocate(1024); + int readChars = 0; + + InputStream is = null; + InputStreamReader isr = null; + try + { + is = url.openStream(); + isr = new InputStreamReader(is); + while (readChars != -1) + { + readChars = isr.read(buffer); + } + buffer.flip(); + } + catch (IOException e) + { + error("The file res/pseudolayout could not be read. cause=" + e.getMessage()); + throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_LayoutFileCouldNotBeRead); + } + finally + { + try + { + if (is != null) + { + is.close(); + } + if (isr != null) + { + isr.close(); + } + } + catch (IOException e) + { + warn("The file " + PSEUDO_LAYOUT_FILE + " could not be closed after reading"); + } + } + + return buffer.toString(); + } + + /** + * Stacks an element + * The stack rules are quite complex. We first start by special cases (in which we analyze the stack + * and the current element for accurate interpretation) and then move to the default cases. + * + * Summarizing, the stack will contain objects from this package (*Bean) as well as String objects. + * When a bean is at the top of the stack, we may perform actions on the given object. Strings are added + * to the stack for bracket matching and to add a mark for future actions. + * + * @param stack The stack where to add elements + * @param model The model being built + * @param elementName The name of the element to add to stack. + */ + private static void addElementsToStack(Stack<Object> stack, LayoutFileModel model, + String elementName) + { + int stackSizeAtStart = stack.size(); + + //-------------- + // SPECIAL CASES + //-------------- + + // When the stack size is equal to zero, we can have one of those two situations: + // + // a) THE LAYOUT FILE CONTAINS MULTIPLE LAYOUT AND/OR PARTS: It is possible to have the + // following tags: "parts", "layouts", "keyboard" or "network". All of them are handled in the + // else clause, by adding the tag name at the stack + // + // b) THE LAYOUT FILE IS SIMPLE (i.e. it doesn't contain layouts, neither a collection + // of parts): It is possible to have the following tags: "display", "background", "button", + // "keyboard", "network". The first three belong to a part definition, so we need to include a + // PartBean to the stack before the object representing the tag. The last two can be handled the same + // way as in item (a) + if (stack.size() == 0) + { + if ((MAIN_LEVEL_DISPLAY.equals(elementName)) + || (MAIN_LEVEL_BACKGROUND.equals(elementName)) + || (MAIN_LEVEL_BUTTON.equals(elementName))) + { + // This is a single part layout. Execute operation described at item (b) above + PartBean bean = model.newPart(); + stack.push(bean); + + if ((MAIN_LEVEL_BACKGROUND.equals(elementName)) + || (MAIN_LEVEL_BUTTON.equals(elementName))) + { + stack.push(elementName); + } + else if (MAIN_LEVEL_DISPLAY.equals(elementName)) + { + RectangleBean display = bean.newDisplay(); + stack.push(display); + } + } + else + { + // PARTS, LAYOUTS, KEYBOARD, NETWORK + stack.push(elementName); + } + } + + // When the stack size is equal to one, we can have one of those four situations: + // + // a) THE ELEMENT AT STACK IS NOT A STRING: In this case, we will handle as default case + // b) THE ELEMENT AT STACK IS THE "parts" STRING: It means that the element name denotes the name of + // a part. We must create a part with the name of the element, and add it to the stack + // c) THE ELEMENT AT STACK IS THE "layouts" STRING: It means that the element name denotes the name of + // a layout. We must create a layout with the name of the element, and add it to the stack + // d) THE ELEMENT AT STACK IS ANY OTHER STRING: In this case, we will handle as default case + else if (stack.size() == 1) + { + Object previousElement = stack.peek(); + if (previousElement instanceof String) + { + if (MAIN_LEVEL_PARTS.equals((String) previousElement)) + { + // elementName is the name of a new part + PartBean bean = model.newPart(elementName); + stack.push(bean); + } + else if (MAIN_LEVEL_LAYOUTS.equals((String) previousElement)) + { + // elementName is the name of a new layout + LayoutBean bean = model.newLayout(elementName); + stack.push(bean); + } + } + } + + //-------------- + // DEFAULT CASES + //-------------- + + // Any other case will be handled below. The following clauses cover any other remaining cases not + // covered by the special cases. The beans created, when added to the stack, represents structures + // already known. If it is not possible to guess what structure we need at the current parse iteration + // or if we need an element at the stack to match a close bracket to come, we simply add it as string + // + // We only execute the following block if the previous cases didn't affect the stack + if (stackSizeAtStart == stack.size()) + { + Object stackElem = stack.peek(); + if (stackElem instanceof PartBean) + { + if (MAIN_LEVEL_DISPLAY.equals(elementName)) + { + RectangleBean display = ((PartBean) stackElem).newDisplay(); + stack.push(display); + } + else if (MAIN_LEVEL_BACKGROUND.equals(elementName)) + { + ImagePositionBean background = + ((PartBean) stackElem).newBackground(elementName); + stack.push(background); + } + else + { + stack.push(elementName); + } + } + else if (stackElem instanceof LayoutBean) + { + PartRefBean bean = ((LayoutBean) stackElem).newPartRef(elementName); + stack.push(bean); + } + else if (stackElem instanceof String) + { + if ((MAIN_LEVEL_BUTTON.equals((String) stackElem) || (PART_BUTTONS + .equals((String) stackElem)))) + { + Object nonStringObj = findFirstNonStringAtStack(stack); + if (nonStringObj != null) + { + PartBean bean = (PartBean) nonStringObj; + ImagePositionBean button = bean.newButton(elementName); + stack.push(button); + } + } + } + } + } + + /** + * Removes an element from the stack + * + * @param stack The stack from where to remove elements + */ + private static void removeElementsFromStack(Stack<Object> stack) + { + stack.pop(); + } + + /** + * Set a key-value pair at the object at the top of the stack. + * Depending on the key-value pair, we may set attributes to the model itself + * + * @param stack The stack containing the element to have a property set + * @param model The model that can have a property set + * @param currentTag The name of the tag containing a model property + * @param key The property key + * @param value The property value + */ + private static void setKeyValuePair(Stack<Object> stack, LayoutFileModel model, + String currentTag, String key, String value) + { + Object obj = stack.peek(); + if (obj instanceof String) + { + Object notStringObj = findFirstNonStringAtStack(stack); + + if (notStringObj instanceof ILayoutBean) + { + ((ILayoutBean) notStringObj).setKeyValue(key, value); + } + else + { + if (MAIN_LEVEL_NETWORK.equals(currentTag)) + { + if (NETWORK_DELAY.equals(key)) + { + model.setNetworkDelay(value); + } + else if (NETWORK_SPEED.equals(key)) + { + model.setNetworkSpeed(value); + } + } + else if (MAIN_LEVEL_KEYBOARD.equals(currentTag)) + { + if (KEYBOARD_CHARMAP.equals(key)) + { + model.setKeyboardCharmap(value); + } + } + } + } + else + { + ((ILayoutBean) obj).setKeyValue(key, value); + } + } + + /** + * Utility method for finding the first non-String object at the stack + * + * @param stack The stack were to find the first non-String at + * + * @return The non-String object + */ + private static Object findFirstNonStringAtStack(Stack<Object> stack) + { + Object firstNonString = null; + Object tmpObj = null; + + int i = stack.size() - 1; + while (i >= 0) + { + tmpObj = stack.get(i); + if (!(tmpObj instanceof String)) + { + firstNonString = tmpObj; + break; + } + else + { + i--; + } + } + + return firstNonString; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/PartBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/PartBean.java new file mode 100644 index 0000000..3a2625f --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/PartBean.java @@ -0,0 +1,154 @@ +/* +* 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.skin.android.parser; + +import java.util.Collection; +import java.util.LinkedHashSet; + +/** + * DESCRIPTION: + * This class represents a part structure from the layout file + * + * RESPONSIBILITY: + * Represent part structures + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by the LayoutFileParser and LayoutFileModel classes only + */ +public class PartBean +{ + /** + * The string used as name of a part, at skins that have a single part + */ + public static final String UNIQUE_PART = "___UNIQUE___"; + + /** + * The part name + */ + private String name; + + /** + * The part background data + */ + private ImagePositionBean background; + + /** + * The part display data + */ + private RectangleBean display; + + /** + * The part buttons + */ + private Collection<ImagePositionBean> buttons = new LinkedHashSet<ImagePositionBean>(); + + /** + * Constructor + * Builds a new part structure with the given name + * + * @param name The layout name + */ + PartBean(String name) + { + this.name = name; + } + + /** + * Creates a new button, registers it and returns it to the user + * + * @param buttonName The name to assign to the button + * + * @return The button + */ + ImagePositionBean newButton(String buttonName) + { + ImagePositionBean bean = new ImagePositionBean(buttonName); + buttons.add(bean); + return bean; + } + + /** + * Creates a new display and registers it + * + * @return The display + */ + RectangleBean newDisplay() + { + display = new RectangleBean(); + return display; + } + + /** + * Creates a new background and registers it + * + * @param bgName The name of the background image + * + * @return The background + */ + ImagePositionBean newBackground(String bgName) + { + background = new ImagePositionBean(bgName); + return background; + } + + /** + * Retrieves the part background information + * + * @return The part background information + */ + ImagePositionBean getBackground() + { + return background; + } + + /** + * Retrieves the part display information + * + * @return The part display information + */ + RectangleBean getDisplay() + { + return display; + } + + /** + * Retrieves the part name + * + * @return The part name + */ + String getName() + { + return name; + } + + Collection<ImagePositionBean> getButtons() + { + return buttons; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "Part: " + name; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/PartRefBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/PartRefBean.java new file mode 100644 index 0000000..72e176d --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/PartRefBean.java @@ -0,0 +1,149 @@ +/* +* 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.skin.android.parser; + +/** + * DESCRIPTION: + * This class represents a part reference. + * It links a layout with a part from the same layout file + * + * RESPONSIBILITY: + * Represent part references and provide link information between parts and layouts + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by the LayoutFileParser and LayoutFileModel classes only + */ +public class PartRefBean implements ILayoutConstants, ILayoutBean +{ + /** + * The name of this part reference + */ + private String refName; + + /** + * The name of the part that this reference points to + */ + private String partName; + + /** + * The X position of the part into the layout + */ + private String x; + + /** + * The Y position of the part into the layout + */ + private String y; + + /** + * The rotation applied to the part, when into a layout + */ + private String rotation; + + /** + * Constructor + * Creates a part reference with the given name + * + * @param refName The part reference name + */ + PartRefBean(String refName) + { + this.refName = refName; + } + + /** + * Retrieves the name of the part being referenced + * + * @return The part name + */ + String getPartName() + { + return partName; + } + + /** + * Retrieves the reference name + * + * @return The reference name + */ + String getRefName() + { + return refName; + } + + /** + * Retrieves the X position where to draw this part at the layout + * + * @return The X position + */ + String getX() + { + return x; + } + + /** + * Retrieves the Y position where to draw this part at the layout + * + * @return The Y position + */ + String getY() + { + return y; + } + + /** + * Retrieves how many rotations to perform on the part when drawing it into the layout + * + * @return How many rotations to perform on the part + */ + String getRotation() + { + return rotation; + } + + @Override + public String toString() + { + return "PartRef: " + refName; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.skin.android.parser.ILayoutBean#setKeyValue(java.lang.String, java.lang.String) + */ + public void setKeyValue(String key, String value) + { + if (ATTR_X.equals(key)) + { + x = value; + } + else if (ATTR_Y.equals(key)) + { + y = value; + } + else if (ATTR_NAME.equals(key)) + { + partName = value; + } + else if (PARTREF_ROTATION.equals(key)) + { + rotation = value; + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/RectangleBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/RectangleBean.java new file mode 100644 index 0000000..0d26f7b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/RectangleBean.java @@ -0,0 +1,127 @@ +/* +* 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.skin.android.parser; + +/** + * DESCRIPTION: + * This class represents a node containing x, y, width and height data + * + * RESPONSIBILITY: + * Represent rectangles of the layout file + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by the LayoutFileParser and LayoutFileModel classes only + */ +public class RectangleBean implements ILayoutConstants, ILayoutBean +{ + /** + * The X position of the rectangle origin + */ + private String xPos; + + /** + * The Y position of the rectangle origin + */ + private String yPos; + + /** + * The width of the rectangle + */ + private String width; + + /** + * The height of the rectangle + */ + private String height; + + /** + * Retrieves the X position of the rectangle origin + * + * @return the X position + */ + String getXPos() + { + return xPos; + } + + /** + * Retrieves the Y position of the rectangle origin + * + * @return the Y position + */ + String getYPos() + { + return yPos; + } + + /** + * Retrieves the width of the rectangle + * + * @return The rectangle width + */ + String getWidth() + { + return width; + } + + /** + * Retrieves the height of the rectangle + * + * @return The rectangle height + */ + String getHeight() + { + return height; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "Rectangle"; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.skin.android.parser.ILayoutBean#setKeyValue(java.lang.String, java.lang.String) + */ + public void setKeyValue(String key, String value) + { + if (ATTR_X.equals(key)) + { + xPos = value; + } + else if (ATTR_Y.equals(key)) + { + yPos = value; + } + else if (ATTR_WIDTH.equals(key)) + { + width = value; + } + else if (ATTR_HEIGHT.equals(key)) + { + height = value; + } + + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/IAndroidUIConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/IAndroidUIConstants.java new file mode 100644 index 0000000..e79f700 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/IAndroidUIConstants.java @@ -0,0 +1,24 @@ +/* +* 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.ui; + +public interface IAndroidUIConstants +{ + /** + * Screens to skin proportion + */ + public static final double DISPLAY_TO_SKIN_RATIO = 0.5; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/IUIHelpConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/IUIHelpConstants.java new file mode 100644 index 0000000..df96437 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/IUIHelpConstants.java @@ -0,0 +1,31 @@ +/* +* 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.ui; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +/** + * DESCRIPTION: + * This interface contains constants used for building ids for context help at UI classes + */ +public interface IUIHelpConstants +{ + // Android Emulator viewer constants + + String EMULATOR_VIEW_HELP = EmulatorPlugin.PLUGIN_ID + ".emulator"; + + String EMULATOR_VIEW_MAIN_DISPLAY_HELP = EmulatorPlugin.PLUGIN_ID + ".maindisplay"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/IAndroidComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/IAndroidComposite.java new file mode 100644 index 0000000..1df03e7 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/IAndroidComposite.java @@ -0,0 +1,80 @@ +/* +* 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.ui.controls; + +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; + +/** + * This interface defines the methods that must be implemented by the + * composites that holds Android Emulator instances. + */ +public interface IAndroidComposite +{ + + /** + * Applies the zoom factor to the components of the composite. + */ + void applyZoomFactor(); + + /** + * Gets the current zoom factor. + * @return the zoom factor + */ + double getZoomFactor(); + + /** + * Sets the zoom factor. + * @param zoom the zoom factor + */ + void setZoomFactor(double zoom); + + /** + * Applies the layout to the components of the composite. + * + * @param layoutName The name of the layout to apply + */ + void applyLayout(String layoutName); + + /** + * Gets is the selected fit to window option. + * @return the zoom factor + */ + boolean isFitToWindowSelected(); + + /** + * Retrieves the key listener to apply to the main display + * + * @return The key listener + */ + KeyListener getKeyListener(); + + /** + * Retrieves the mouse listener to apply to the main display + * + * @return The mouse listener + */ + MouseListener getMouseListener(); + + /** + * Retrieves the mouse move listener to apply to the main display + * + * @return The mouse move listener + */ + MouseMoveListener getMouseMoveListener(); + +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/RemoteCLIDisplay.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/RemoteCLIDisplay.java new file mode 100644 index 0000000..41e6083 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/RemoteCLIDisplay.java @@ -0,0 +1,277 @@ +/* +* 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.ui.controls; + +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.ISWTPainter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; + +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; + +/** + * DESCRIPTION: + * This class implements the composite that displays the contents of the + * CLI display, according to the provided painter object. + * + * RESPONSIBILITY: + * - Display the contents of the CLI display at screen + * + * COLABORATORS: + * None. + * + * USAGE: + * Add the composite to a view or other equivalent SWT object for it to + * display the result of the communication through VNC Protocol + * + */ +public class RemoteCLIDisplay extends Composite +{ + private Canvas canvas; + + private Image screen = null; + + private ISWTPainter painter; + + private Timer refreshTimer; + + private static long FIRST_REFRESH_DELAY_MS = 500; /* Time in milliseconds for the first update */ + + private static long REFRESH_DELAY_PERIOD_MS = 300; /* Time in milliseconds between 2 updates */ + + private boolean active = false; + + private double zoomFactor = 1; + + /** + * Creates a new RemoteCLIDisplay object. + * + * @param parent The parent composite + * @param cliPainter The object where to retrieve pixels from + */ + public RemoteCLIDisplay(Composite parent, ISWTPainter cliPainter) + { + super(parent, SWT.BACKGROUND); + this.painter = cliPainter; + this.setLayout(parent.getLayout()); + canvas = new Canvas(this, SWT.BACKGROUND); + } + + /** + * Starts the display refresh + */ + synchronized public void start() + { + addRefreshTimer(); + setRunning(true); + } + + /** + * Stops the display refresh + */ + synchronized public void stop() + { + setRunning(false); + refreshTimer.cancel(); + + if (!canvas.isDisposed()) + { + GC gc = new GC(canvas); + canvas.drawBackground(gc, 0, 0, canvas.getSize().x, canvas.getSize().y); + gc.dispose(); + } + } + + /** + * Adds a timer that schedules the screen's update in a fixed period. + */ + private void addRefreshTimer() + { + refreshTimer = new Timer(); + + final IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this); + + refreshTimer.scheduleAtFixedRate(new TimerTask() + { + @Override + public void run() + { + if (instance.getHasCli()) + { + // Request CLI Update here (when applicable) + + getDisplay().syncExec(new Runnable() + { + public void run() + { + updateScreen(); + } + }); + } + } + }, FIRST_REFRESH_DELAY_MS, REFRESH_DELAY_PERIOD_MS); + } + + /** + * Performs the screen update itself + */ + private void updateScreen() + { + if (screen != null) + { + screen.dispose(); + } + + if ((!getDisplay().isDisposed()) && (isDisplayActive())) + { + if ((painter.getImageData() != null) && (!canvas.isDisposed())) + { + screen = + new Image(canvas.getDisplay(), painter.getImageData().scaledTo( + (int) (painter.getImageData().width * zoomFactor), + (int) (painter.getImageData().height * zoomFactor))); + + GC gc = new GC(canvas); + gc.drawImage(screen, 0, 0); + gc.dispose(); + } + } + else + { + stop(); + } + } + + /** + * Returns true if the component is running. + */ + synchronized public boolean isDisplayActive() + { + return active; + } + + /** + * Gets the Canvas used to show the screen. + * + * @return the Canvas object. + */ + public Canvas getCanvas() + { + return canvas; + } + + /** + * Retrieves the image being drawn at display + * + * @return The image being drawn at display + */ + public Image getScreen() + { + return screen; + } + + /** + * Retrieves the display width + * + * @return The display width + */ + public int getScreenWidth() + { + return painter.getWidth(); + } + + /** + * Retrieves the display height + * + * @return The display height + */ + public int getScreenHeight() + { + return painter.getHeight(); + } + + /** + * Sets the current state of the display + * + * @param running true if the display is refreshing; false otherwise + */ + synchronized private void setRunning(boolean running) + { + this.active = running; + } + + /** + * @see org.eclipse.swt.widgets.Widget#dispose() + */ + @Override + public void dispose() + { + if (isDisplayActive()) + { + stop(); + } + + if (screen != null) + { + screen.dispose(); + } + + canvas.dispose(); + super.dispose(); + } + + /** + * @see org.eclipse.swt.widgets.Control#setBackground(Color) + */ + @Override + public void setBackground(Color color) + { + super.setBackground(color); + canvas.setBackground(color); + } + + /** + * Retrieves the current zoom factor being applied to the screen + * + * @return The current zoom factor + */ + public double getZoomFactor() + { + return zoomFactor; + } + + /** + * Sets a new zoom factor to the screen + * + * @param zoomFactor The zoom factor to set to the screen + */ + public void setZoomFactor(double zoomFactor) + { + this.zoomFactor = zoomFactor; + + IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this); + if (instance.getHasCli()) + { + updateScreen(); + } + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/UIHelper.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/UIHelper.java new file mode 100644 index 0000000..751811d --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/UIHelper.java @@ -0,0 +1,145 @@ +/* +* 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.ui.controls; + +import org.eclipse.sequoyah.vnc.vncviewer.graphics.IRemoteDisplay; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; + +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.ui.controls.maindisplay.MainDisplayComposite; + +/** + * DESCRIPTION: + * This class provides helper methods for Android Composite UI + * + * RESPONSIBILITY: + * - Provide utility methods related to Android Composite UI + * + * COLABORATORS: + * None. + * + * USAGE: + * Use any of the public methods for getting help with skin UI + */ +public class UIHelper +{ + /** + * Retrieves the instance related to a given control + * + * @param control The control associated to an instance, which is to be retrieved + * + * @return The instance associated with the control + */ + public static IAndroidEmulatorInstance getInstanceAssociatedToControl(Control control) + { + IAndroidEmulatorInstance result = null; + Control composite = null; + TabFolder folder = null; + + if (control instanceof ScrolledComposite) + { + composite = ((ScrolledComposite) control).getContent(); + folder = (TabFolder) composite.getParent(); + } + else if (control instanceof MainDisplayComposite) + { + composite = control.getParent(); + folder = (TabFolder) control.getParent().getParent(); + } + else if ((control instanceof Composite) && (control instanceof IAndroidComposite)) + { + composite = control; + folder = (TabFolder) control.getParent(); + } + else if ((control instanceof SWTRemoteDisplay) || (control instanceof RemoteCLIDisplay)) + { + composite = control.getParent(); + folder = (TabFolder) composite.getParent(); + } + + if (folder != null) + { + TabItem[] items = folder.getItems(); + for (TabItem item : items) + { + if (item.getControl() == composite) + { + result = (IAndroidEmulatorInstance) item.getData(); + break; + } + } + } + + return result; + } + + public static SWTRemoteDisplay getRemoteDisplayAssociatedToControl(Control control) + { + + SWTRemoteDisplay remoteDisplay = null; + + if ((control instanceof Composite) && (control instanceof IAndroidComposite)) + { + for (Control childControl : ((Composite) control).getChildren()) + { + if (childControl instanceof SWTRemoteDisplay) + { + remoteDisplay = (SWTRemoteDisplay) childControl; + break; + } + } + } + + return remoteDisplay; + } + + /** + * Ajusts the x,y coordinates of the mouse event according to the current zoom and rotation. + * Coordinates are originally set with the x,y coordinates of the UI element which may be resized according to zoom and rotation, + * but the x,y coordinates expected by the Android emulator is independent of the zoom and rotation. + * @param e the mouse event whose coordinates will be ajusted. + */ + public static void ajustCoordinates(MouseEvent e, IAndroidComposite composite) + { + int x; + int y; + + SWTRemoteDisplay mainDisplay = + UIHelper.getRemoteDisplayAssociatedToControl((Control) composite); + IRemoteDisplay.Rotation rotation = mainDisplay.getRotation(); + double zoomFactor = composite.getZoomFactor(); + + switch (rotation) + { + case ROTATION_90DEG_COUNTERCLOCKWISE: + x = mainDisplay.getScreenWidth() - (int) ((double) e.y / zoomFactor); + y = (int) ((double) e.x / zoomFactor); + break; + default: + x = (int) ((double) e.x / zoomFactor); + y = (int) ((double) e.y / zoomFactor); + } + + e.x = x; + e.y = y; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/maindisplay/MainDisplayComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/maindisplay/MainDisplayComposite.java new file mode 100644 index 0000000..c5a4561 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/maindisplay/MainDisplayComposite.java @@ -0,0 +1,470 @@ +/* +* 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.ui.controls.maindisplay; + +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.Properties; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.IRemoteDisplay; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseWheelListener; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.model.IInputLogic; +import com.motorola.studio.android.emulator.logic.AndroidLogicUtils; +import com.motorola.studio.android.emulator.skin.android.AndroidSkinTranslator; +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.emulator.ui.controls.UIHelper; +import com.motorola.studio.android.emulator.ui.handlers.IHandlerConstants; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * This class is the composite that holds the main display and it is shown by + * the Emulator Main Display View. + */ +public class MainDisplayComposite extends Composite implements IAndroidComposite +{ + + /** + * The zoom factor whose default value is 1.0 (100%) + */ + private double zoomFactor = 1.0; + + private double fitZoomfactor; + + // Minimum value to be used as zoom factor. This is necessary to avoid + // divisions to zero + private static final double MINIMUM_ZOOM_FACTOR = 0.0001; + + private static final double ZOOM_FIT = 0.0; + + /** + * The flag indicating that Ctrl key is pressed + */ + private boolean ctrlPressed = false; + + /** + * SWT key pressed/released events listener. + */ + private KeyListener keyListener; + + private MouseListener mouseListener; + + private MouseMoveListener mouseMoveListener; + + private IInputLogic androidInput; + + private boolean isMouseLeftButtonPressed; + + private boolean isFitToWindow; + + private IAndroidEmulatorInstance androidInstance; + + private Properties keyMap; + + /** + * Constructor + * + * @param parent + * composite + * @param style + * style + * @param baseWidth + * the default main display width + * @param baseHeight + * the default main display height + */ + public MainDisplayComposite(Composite parent, int style, int baseWidth, int baseHeight, + IAndroidEmulatorInstance instance) + { + super(parent, style); + + androidInput = instance.getInputLogic(); + + androidInstance = instance; + + isMouseLeftButtonPressed = false; + + keyMap = AndroidSkinTranslator.getQwertyKeyMap(); + + addListener(); + + if (!Platform.getOS().equals(Platform.OS_MACOSX)) + { + hideEmulatorWindow(); + } + + } + + private void hideEmulatorWindow() + { + int port = + AndroidLogicUtils.getEmulatorPort(DDMSFacade.getSerialNumberByName(androidInstance + .getName())); + long windowHandle = NativeUIUtils.getWindowHandle(androidInstance.getName(), port); + androidInstance.setWindowHandle(windowHandle); + + NativeUIUtils.hideWindow(windowHandle); + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.widgets.Widget#dispose() + */ + @Override + public void dispose() + { + if (androidInput != null) + { + androidInput.dispose(); + } + + keyListener = null; + mouseListener = null; + mouseMoveListener = null; + + if (!Platform.getOS().equals(Platform.OS_MACOSX)) + { + long hnd = androidInstance.getWindowHandle(); + if (hnd > 0) + { + NativeUIUtils.showWindow(hnd); + NativeUIUtils.restoreWindow(hnd); + } + + //Force update on redrawing + androidInstance.setWindowHandle(0); + } + + super.dispose(); + } + + /** + * Updates the composite size when zoom is changed. + * + * @param zoom + * the zoom factor + */ + public void setZoomFactor(double zoomFactor) + { + info("Update detached view composite size"); + if (zoomFactor == ZOOM_FIT) + { + isFitToWindow = true; + } + else + { + isFitToWindow = false; + } + this.zoomFactor = zoomFactor; + } + + /** + * Gets the zoom factor. + * + * @return zoom the zoom factor. + */ + public double getZoomFactor() + { + return zoomFactor; + } + + /** + * Applies the zoom factor to the components of the composite, updating the + * composite size to hold totally the main display. + */ + public void applyZoomFactor() + { + + SWTRemoteDisplay mainDisplay = UIHelper.getRemoteDisplayAssociatedToControl(this); + IRemoteDisplay.Rotation rotation = mainDisplay.getRotation(); + + int baseHeight; + int baseWidth; + + switch (rotation) + { + case ROTATION_90DEG_COUNTERCLOCKWISE: + baseHeight = mainDisplay.getScreenWidth(); + baseWidth = mainDisplay.getScreenHeight(); + break; + default: + baseHeight = mainDisplay.getScreenHeight(); + baseWidth = mainDisplay.getScreenWidth(); + + } + + int width; + int height; + if (isFitToWindow) + { + Rectangle clientArea = getParent().getClientArea(); + if ((clientArea.width == 0) || (clientArea.height == 0)) + { + // zoom factor cannot be zero, otherwise an + // IllegalArgumentException + // is raised in some SWT methods + fitZoomfactor = MINIMUM_ZOOM_FACTOR; + } + else + { + double widthRatio = (double) (clientArea.width) / baseWidth; + double heightRatio = (double) (clientArea.height) / baseHeight; + fitZoomfactor = Math.min(widthRatio, heightRatio); + } + width = new Double(baseWidth * fitZoomfactor).intValue(); + height = new Double(baseHeight * fitZoomfactor).intValue(); + + if (mainDisplay != null) + { + mainDisplay.setZoomFactor(fitZoomfactor); + } + } + else + { + width = new Double(baseWidth * zoomFactor).intValue(); + height = new Double(baseHeight * zoomFactor).intValue(); + + if (mainDisplay != null) + { + mainDisplay.setZoomFactor(zoomFactor); + } + } + + setSize(width, height); + } + + /** + * Adds listener for SWT events. + */ + private void addListener() + { + // add listener to handle keyboard key pressing + keyListener = new KeyListener() + { + + public void keyPressed(KeyEvent arg0) + { + + int keyCode = arg0.keyCode; + + if (keyCode == SWT.CTRL) + { + ctrlPressed = true; + } + else + { + // send message to emulator + androidInput.sendKey(arg0.character, keyCode, keyMap); + } + + } + + public void keyReleased(KeyEvent arg0) + { + int keyCode = arg0.keyCode; + + if (keyCode == SWT.CTRL) + { + ctrlPressed = false; + } + } + + }; + + // listener to change the zoom factor using Ctrl + Mouse Wheel + addMouseWheelListener(new MouseWheelListener() + { + + public void mouseScrolled(MouseEvent event) + { + if (ctrlPressed) + { + + if ((event.count > 0) && (zoomFactor < IHandlerConstants.MAXIMUM_ZOOM)) + { + // increase zoom factor + setZoomFactor(zoomFactor + IHandlerConstants.STEP_ZOOM); + applyZoomFactor(); + } + + else if ((event.count < 0) && (zoomFactor > IHandlerConstants.MINIMUM_ZOOM)) + { + // decrease zoom factor + setZoomFactor(zoomFactor - IHandlerConstants.STEP_ZOOM); + applyZoomFactor(); + } + } + } + }); + + mouseListener = new MouseAdapter() + { + /** + * @see org.eclipse.swt.events.MouseListener#mouseUp(MouseEvent) + */ + @Override + public void mouseUp(MouseEvent e) + { + handleMouseUp(e); + } + + /** + * @see org.eclipse.swt.events.MouseListener#mouseDown(MouseEvent) + */ + @Override + public void mouseDown(MouseEvent e) + { + setFocus(); + handleMouseDown(e); + } + }; + + mouseMoveListener = new MouseMoveListener() + { + /** + * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(MouseEvent) + */ + public void mouseMove(MouseEvent e) + { + handleMouseMove(e); + } + }; + + getParent().addControlListener(new ControlAdapter() + { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent) + */ + @Override + public void controlResized(ControlEvent event) + { + if (isFitToWindow) + { + applyZoomFactor(); + } + } + }); + + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.ui.controls.IAndroidComposite#applyLayout(java.lang.String) + */ + public void applyLayout(String layoutName) + { + //do nothing + } + + /** + * Gets the listener that handles SWT key pressing and releasing events. + * + * @return the KeyListener object + */ + public KeyListener getKeyListener() + { + return keyListener; + } + + /** + * Gets the listener that handles SWT mouse clicking events. + * + * @return the MouseListener object + */ + public MouseListener getMouseListener() + { + return mouseListener; + } + + /** + * Gets the listener that handles SWT mouse moving events. + * + * @return the MouseMoveListener object + */ + public MouseMoveListener getMouseMoveListener() + { + return mouseMoveListener; + } + + /** + * Handles the mouse up event on the skin composite + * + * @param e + * The mouse up event + */ + private void handleMouseUp(MouseEvent e) + { + if (e.button == 1) + { + isMouseLeftButtonPressed = false; + UIHelper.ajustCoordinates(e, this); + androidInput.sendMouseUp(e.x, e.y); + } + } + + /** + * Handles the mouse down event on the skin composite + * + * @param e + * The mouse down event + */ + private void handleMouseDown(MouseEvent e) + { + if (e.button == 1) + { + UIHelper.ajustCoordinates(e, this); + androidInput.sendMouseDown(e.x, e.y); + isMouseLeftButtonPressed = true; + } + + } + + /** + * Handles the mouse move event on the skin composite + * + * @param e + * The mouse move event + */ + private void handleMouseMove(MouseEvent e) + { + if (isMouseLeftButtonPressed) + { + UIHelper.ajustCoordinates(e, this); + androidInput.sendMouseMove(e.x, e.y); + } + } + + public boolean isFitToWindowSelected() + { + return isFitToWindow; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/nativewindow/NativeWindowComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/nativewindow/NativeWindowComposite.java new file mode 100644 index 0000000..1658f98 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/nativewindow/NativeWindowComposite.java @@ -0,0 +1,621 @@ +/* +* 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.ui.controls.nativewindow; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.AndroidPlugin; +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.common.preferences.DialogWithToggleUtils; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.model.IInputLogic; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.utils.TelnetAndroidInput; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.AndroidLogicUtils; +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +public class NativeWindowComposite extends ScrolledComposite implements IAndroidComposite +{ + /** + * Preference key of the Question Dialog about changing zoom + * + */ + private static String LOOSE_ORIGINAL_SCALE_KEY_PREFERENCE = "loose.original.scale"; + + //Constants + private static final double MINIMUM_ZOOM_FACTOR = 0.10; + + private static final double ZOOM_FIT = 0.0; + + private Composite contentComposite; + + private IAndroidEmulatorInstance androidInstance; + + private long windowHandle; + + private long originalParentHandle; + + private long windowProperties; + + private Point windowSize; + + private Point nativeWindowSize; + + private NativeWindowMonitor nativeWindowMonitor; + + protected boolean resizing; + + private boolean isFitToWindow; + + private double zoomFactor = 0.99; + + private double fitZoomFactor = ZOOM_FIT; + + private boolean forceNativeWindowSizeUpdate; + + private boolean isOriginalScale; + + private boolean zoomLocked; + + private class NativeWindowMonitor extends Timer + { + private Timer timer; + + private MonitorTask monitorTask; + + public NativeWindowMonitor(long interval) + { + timer = new Timer(); + monitorTask = new MonitorTask(); + timer.schedule(monitorTask, interval, interval); + } + + private class MonitorTask extends TimerTask + { + @Override + public void run() + { + Point newWindowSize = + NativeUIUtils.getWindowSize(originalParentHandle, windowHandle); + if ((windowHandle <= 0) || !newWindowSize.equals(windowSize)) + { + Display display = Display.getDefault(); + if (!display.isDisposed()) + { + try + { + display.syncExec(new Runnable() + { + public void run() + { + updateContentComposite(); + } + }); + } + catch (SWTException e) + { + //Do nothing in case the widget is disposed, occurs when the tool is closing. + } + } + } + + if (NativeUIUtils.isWindowEnabled(windowHandle)) + { + Display display = Display.getDefault(); + if (!display.isDisposed()) + { + try + { + display.syncExec(new Runnable() + { + public void run() + { + if (!contentComposite.isDisposed()) + { + contentComposite.forceFocus(); + } + } + }); + } + catch (SWTException e) + { + //Do nothing in case the widget is disposed, occurs when the tool is closing. + } + } + } + } + } + + public void stopMonitoring() + { + timer.cancel(); + timer = null; + monitorTask = null; + } + } + + public NativeWindowComposite(Composite parent, IAndroidSkin androidSkin, + final IAndroidEmulatorInstance instance) + { + super(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + + info("Creating Native Window Composite for " + instance.getName()); + + getVerticalBar().setEnabled(true); + getHorizontalBar().setEnabled(true); + this.setLayout(new FillLayout()); + + androidInstance = instance; + + nativeWindowMonitor = new NativeWindowMonitor(500); + + addControlListener(new ControlAdapter() + { + final boolean[] running = new boolean[1]; + + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent) + */ + @Override + public void controlResized(ControlEvent event) + { + if (isFitToWindow) + { + try + { + Thread.sleep(200); + } + catch (InterruptedException e) + { + //do nothing + } + + if (running[0]) + { + return; + } + running[0] = true; + Display.getCurrent().asyncExec(new Runnable() + { + public void run() + { + running[0] = false; + if (!getShell().isDisposed()) + { + calculateFitZoomFactor(forceNativeWindowSizeUpdate); + applyZoomFactor(); + } + + } + }); + } + } + }); + + createContentComposite(instance); + info("Created Native Window Composite for " + instance.getName()); + } + + /** + * Creates the content composite that will be parent of emulator native window + * + * @param instance A android instance from which the composite could be retrieved if it is already created + */ + public void createContentComposite(IAndroidEmulatorInstance instance) + { + contentComposite = instance.getComposite(); + if (contentComposite != null) + { + info("Instance already has a composite"); + contentComposite.setParent(this); + contentComposite.setVisible(true); + } + else + { + contentComposite = new Composite(this, SWT.EMBEDDED | SWT.NO_BACKGROUND); + } + + this.setContent(contentComposite); + if (instance.getProperties().getProperty("Command_Line").contains("-scale")) + { + isOriginalScale = true; + } + + //Force to update native window size at 100% when first using nativeWindowSize field + forceNativeWindowSizeUpdate = true; + + //Avoid perform apply zoom factor when creating composite + zoomLocked = true; + draw(); + } + + /** + * Changes the parent from OS to content composite keeping the original properties and parent window reference + */ + private void draw() + { + if (contentComposite != null) + { + windowHandle = androidInstance.getWindowHandle(); + + //If the instance does not contain the window handle, it should be retrieved + //from native emulator window and assigned to instance + if (windowHandle <= 0) + { + int port = + AndroidLogicUtils.getEmulatorPort(DDMSFacade + .getSerialNumberByName(androidInstance.getName())); + windowHandle = NativeUIUtils.getWindowHandle(androidInstance.getName(), port); + + androidInstance.setWindowHandle(windowHandle); + } + + if ((windowProperties <= 0) && (windowHandle > 0)) + { + windowProperties = NativeUIUtils.getWindowProperties(windowHandle); + info("Native Window Properties:" + windowProperties); + } + + //Set Window Style + if (windowHandle > 0) + { + NativeUIUtils.setWindowStyle(windowHandle); + } + + if (originalParentHandle <= 0) + { + originalParentHandle = windowHandle; + } + + //Retrieve window size before changing parent + if (windowHandle > 0) + { + windowSize = NativeUIUtils.getWindowSize(originalParentHandle, windowHandle); + } + + //Set the new Parent and store the original parent + if ((originalParentHandle <= 0) || (originalParentHandle == windowHandle)) + { + if (windowHandle > 0) + { + originalParentHandle = + NativeUIUtils.embedWindow(windowHandle, contentComposite); + info("Native Window Parent:" + originalParentHandle); + } + } + else + { + NativeUIUtils.embedWindow(windowHandle, contentComposite); + } + + if (windowSize == null) + { + windowSize = new Point(700, 500); + } + + //Update composite size + contentComposite + .setSize(contentComposite.computeSize(windowSize.x, windowSize.y, true)); + contentComposite.redraw(); + this.update(); + + this.setMinSize(contentComposite.computeSize(windowSize.x, windowSize.y)); + this.layout(); + } + else + { + createContentComposite(androidInstance); + } + } + + public void changeToNextLayout() + { + contentComposite.setVisible(false); + + contentComposite.setLocation(0, 0); + + NativeUIUtils.sendNextLayoutCommand(originalParentHandle, windowHandle); + + updateContentComposite(); + + forceNativeWindowSizeUpdate = true; + if (isFitToWindow) + { + //Force update to fit zoom factor + setZoomFactor(ZOOM_FIT); + } + applyZoomFactor(); + + try + { + Thread.sleep(200); + } + catch (InterruptedException e) + { + //do nothing + } + + contentComposite.setVisible(true); + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.widgets.Widget#dispose() + */ + @Override + public void dispose() + { + info("Disposing Native Window Composite"); + if (nativeWindowMonitor != null) + { + nativeWindowMonitor.stopMonitoring(); + nativeWindowMonitor = null; + info("Disposed Native Window Monitor"); + } + if (windowHandle > 0) + { + info("Restoring original properties for window: " + windowHandle); + NativeUIUtils.setWindowProperties(windowHandle, windowProperties); + + boolean shallUnembed = + AndroidPlugin.getDefault().getPreferenceStore() + .getBoolean(AndroidPlugin.SHALL_UNEMBED_EMULATORS_PREF_KEY); + if ((originalParentHandle > 0) && shallUnembed) + { + info("Setting original parent: " + originalParentHandle + " for window" + + windowHandle); + NativeUIUtils.unembedWindow(windowHandle, originalParentHandle); + //Force update when redrawing + androidInstance.setWindowHandle(0); + info("Restoring window: " + windowHandle); + NativeUIUtils.restoreWindow(windowHandle); + } + + } + + if (!Platform.getOS().equals(Platform.OS_WIN32)) + { + info("Trying to store the content composite in instance"); + if (contentComposite != null) + { + info("Is instance started? :" + androidInstance.isStarted()); + if (androidInstance.isStarted()) + { + try + { + contentComposite.setParent(PlatformUI.getWorkbench() + .getActiveWorkbenchWindow().getShell()); + this.setContent(null); + contentComposite.setVisible(true); + androidInstance.setComposite(contentComposite); + } + catch (Exception e) + { + error("Error trying to store the content composite :" + e.getMessage()); + } + } + } + } + super.dispose(); + } + + /** + * Apply the zoom factor to the instance + */ + public void applyZoomFactor() + { + if (!isOriginalScale && !zoomLocked) + { + contentComposite.setLocation(0, 0); + IInputLogic inputLogic = androidInstance.getInputLogic(); + TelnetAndroidInput telnetAndroidInput = (TelnetAndroidInput) inputLogic; + NativeUIUtils.hideWindow(windowHandle); + + if (isFitToWindow) + { + telnetAndroidInput.sendWindowScale(fitZoomFactor); + } + else + { + telnetAndroidInput.sendWindowScale(zoomFactor); + } + + try + { + Thread.sleep(200); + } + catch (InterruptedException e) + { + //do nothing + } + telnetAndroidInput.dispose(); + NativeUIUtils.showWindow(windowHandle); + updateContentComposite(); + } + } + + void calculateFitZoomFactor(boolean requireNativeSizeUpdate) + { + // Compute new zoom factor if the zoom mode is "Fit to Window" + Rectangle clientArea = getClientArea(); + if ((clientArea.width == 0) || (clientArea.height == 0)) + { + // zoom factor cannot be zero, otherwise an + // IllegalArgumentException + // is raised in some SWT methods + fitZoomFactor = MINIMUM_ZOOM_FACTOR; + } + else + { + // if the layout was changed, it is needed to retrieve the native window size at 100% + // that size is required to the correct ratio calculus + if (requireNativeSizeUpdate) + { + forceNativeWindowSizeUpdate = false; + updateNativeWindowSize(); + } + else + { + double widthRatio = (double) (clientArea.width) / nativeWindowSize.x; + double heightRatio = (double) (clientArea.height) / nativeWindowSize.y; + fitZoomFactor = + (Math.min(widthRatio, heightRatio) > MINIMUM_ZOOM_FACTOR ? Math.min( + widthRatio, heightRatio) : MINIMUM_ZOOM_FACTOR); + } + } + } + + /** + * This method brings the emulator window to 100% zoom factor to retrieve their native size + */ + private void updateNativeWindowSize() + { + info("Updating Native Window Size"); + setZoomFactor(1.0d); + applyZoomFactor(); + + nativeWindowSize = NativeUIUtils.getWindowSize(originalParentHandle, windowHandle); + + setZoomFactor(ZOOM_FIT); + applyZoomFactor(); + info("Updated Native Window Size"); + } + + private void updateContentComposite() + { + if (!this.isDisposed()) + { + windowSize = NativeUIUtils.getWindowSize(originalParentHandle, windowHandle); + if (windowSize != null) + { + if ((contentComposite != null) && !contentComposite.isDisposed()) + { + contentComposite.setSize(windowSize.x, windowSize.y); + contentComposite.redraw(); + } + this.setMinSize(windowSize.x, windowSize.y); + draw(); + this.redraw(); + info("Updated Content Composite"); + } + } + } + + /** + * Gets the current zoom factor. + * + * @return the zoom factor + */ + public double getZoomFactor() + { + if (isFitToWindow) + { + return fitZoomFactor; + } + return zoomFactor; + } + + /** + * Sets the zoom factor. + * + * @param zoom the zoom factor + * + */ + public void setZoomFactor(double zoom) + { + boolean execute = true; + zoomLocked = false; + if (isOriginalScale) + { + execute = + DialogWithToggleUtils.showQuestion(LOOSE_ORIGINAL_SCALE_KEY_PREFERENCE, + EmulatorNLS.QUESTION_NativeWindow_LooseOriginalScale_Title, + EmulatorNLS.QUESTION_NativeWindow_LooseOriginalScale_Text); + } + if (execute) + { + isOriginalScale = false; + + if (zoom == ZOOM_FIT) + { + isFitToWindow = true; + calculateFitZoomFactor(forceNativeWindowSizeUpdate); + } + else + { + isOriginalScale = false; + isFitToWindow = false; + } + zoomFactor = zoom; + } + } + + @Override + public boolean setFocus() + { + NativeUIUtils.setWindowFocus(windowHandle); + return super.setFocus(); + } + + public void applyLayout(String layoutName) + { + setLayout(new FillLayout()); + draw(); + } + + public KeyListener getKeyListener() + { + return null; + } + + public MouseListener getMouseListener() + { + return null; + } + + public MouseMoveListener getMouseMoveListener() + { + return null; + } + + public boolean isFitToWindowSelected() + { + return isFitToWindow; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/skin/AndroidSkinLayout.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/skin/AndroidSkinLayout.java new file mode 100644 index 0000000..11fadd5 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/skin/AndroidSkinLayout.java @@ -0,0 +1,507 @@ +/* +* 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.ui.controls.skin; + +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Layout; + +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.skin.AndroidSkinBean; +import com.motorola.studio.android.emulator.core.skin.ISkinKeyXmlTags; +import com.motorola.studio.android.emulator.ui.IAndroidUIConstants; +import com.motorola.studio.android.emulator.ui.controls.RemoteCLIDisplay; +import com.motorola.studio.android.emulator.ui.controls.UIHelper; + +/** + * DESCRIPTION: + * This class implements the layout used to draw skins + * It is a very specific implementation that applies only to Android Emulator + * The position and size of the widgets are read from the skin bean provided + * during layout construction + * + * RESPONSIBILITY: + * - Draw the widgets that composes the skin in the correct position and + * with the correct size + * + * COLABORATORS: + * None. + * + * USAGE: + * Use of this class is restricted to the classes in this package + */ +class AndroidSkinLayout extends Layout +{ + /** + * Object that holds information from the skin.xml file + * of the skin being currently used + */ + private final AndroidSkinBean skin; + + /** + * Reference to the CLI display that will be placed by this layout + */ + private RemoteCLIDisplay cliDisplayChild = null; + + /** + * Reference to the main display that will be placed by this layout + */ + private SWTRemoteDisplay mainDisplayChild = null; + + /** + * Flag that indicates if the skin contains an open external display + * placeholder + */ + private final boolean openExternalDisplayAvailable; + + /** + * Flag that indicates if the skin contains an external display + * placeholder + */ + private final boolean externalDisplayAvailable; + + /** + * Flag that indicates that the flip is supported. This flag has nothing to do with slide + */ + private final boolean isFlipSupported; + + /** + * Creates a new AndroidSkinLayout object. + * As the image dimensions are not provided with this constructor, it is not possible + * to have automatic zoom factor calculation. Using this constructor will set the + * initial zoom policy to "100%" + * + * @param skin An skin bean containing the parameters used to place the + * widgets + * @param isFlipSupported Flag that indicates if flip (not slide) is supported + */ + AndroidSkinLayout(AndroidSkinBean skin, boolean isFlipSupported) + { + this.skin = skin; + + this.isFlipSupported = isFlipSupported; + openExternalDisplayAvailable = skin.isOpenExternalDisplayAvailable(); + externalDisplayAvailable = skin.isExternalDisplayAvailable(); + } + + /** + * @see org.eclipse.swt.widgets.Layout#computeSize(Composite, int, int, boolean) + */ + @Override + protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) + { + if (!(composite instanceof SkinComposite)) + { + throw new IllegalArgumentException(); + } + + Point size; + + // Retrieve needed data from composite + IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(composite); + boolean flipSlideClosed = false; + if (instance != null) + { + //flipSlideClosed = instance.isFlipSlideClosed(); + flipSlideClosed = false; + } + double zoomFactor = ((SkinComposite) composite).getZoomFactor(); + + if (externalDisplayAvailable) + { + if (openExternalDisplayAvailable) + { + if (!flipSlideClosed) + { + // Scenario 1: + // A) Available displays: INTERNAL, OPEN EXTERNAL, EXTERNAL + // B) Flip is open + // C) Displays being showed: INTERNAL, OPEN EXTERNAL + size = allAvailable(zoomFactor); + } + else + { + // Scenario 2: + // A) Available displays: INTERNAL, OPEN EXTERNAL, EXTERNAL + // B) Flip is closed + // C) Display being showed: EXTERNAL + size = flipClosed(zoomFactor); + } + } + else + { + if (!flipSlideClosed) + { + // Scenario 3: + // A) Available displays: INTERNAL, EXTERNAL + // B) Flip is opened + // C) Display being showed: INTERNAL + size = openExternalUnavailable(zoomFactor); + } + else + { + // Scenario 4: + // A) Available displays: INTERNAL, EXTERNAL + // B) Flip is closed + // C) Display being showed: EXTERNAL + size = openExternalUnavailableAndFlipClosed(zoomFactor); + } + } + } + else + { + // Scenario 5: + // A) Available display: INTERNAL + // B) Flip is opened or closed + // C) Display being showed: INTERNAL + size = onlyInternal(zoomFactor); + } + + return size; + } + + /** + * This method is called by computeSize when all displays are available and the flip is opened + * + * @param zoomFactor The zoom factor used to calculate the size + * + * @return The size + */ + private Point allAvailable(double zoomFactor) + { + Point size; + + int x1 = skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_X); + int y1 = + Math.min(skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_Y), + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_Y)); + int x2 = + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_X) + + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_WIDTH); + int y2 = + Math.max( + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_Y) + + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT), + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_Y) + + skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_HEIGHT)); + + size = new Point((int) ((x2 - x1) * zoomFactor), (int) ((y2 - y1) * zoomFactor)); + + return size; + } + + /** + * This method is called by computeSize when all displays are available and the flip is closed + * + * @param zoomFactor The zoom factor used to calculate the size + * + * @return The size + */ + private Point flipClosed(double zoomFactor) + { + Point size = + new Point( + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_WIDTH) * zoomFactor), + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_HEIGHT) * zoomFactor)); + + return size; + } + + /** + * This method is called by computeSize when all displays but open external display are available + * and the flip is opened + * + * @param zoomFactor The zoom factor used to calculate the size + * + * @return The size + */ + private Point openExternalUnavailable(double zoomFactor) + { + Point size = + new Point( + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH) * zoomFactor), + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT) * zoomFactor)); + + return size; + } + + /** + * This method is called by computeSize when all displays but open external display are available + * and the flip is closed + * + * @param zoomFactor The zoom factor used to calculate the size + * + * @return The size + */ + private Point openExternalUnavailableAndFlipClosed(double zoomFactor) + { + Point size = + new Point( + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_WIDTH) * zoomFactor), + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_HEIGHT) * zoomFactor)); + + return size; + } + + /** + * This method is called by computeSize when only the internal display is available + * + * @param zoomFactor The zoom factor used to calculate the size + * + * @return The size + */ + private Point onlyInternal(double zoomFactor) + { + Point size = + new Point( + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH) * zoomFactor), + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT) * zoomFactor)); + + return size; + } + + /** + * @see org.eclipse.swt.widgets.Layout#layout(Composite, boolean) + */ + @Override + protected void layout(Composite composite, boolean flushCache) + { + if (!(composite instanceof SkinComposite)) + { + // If this composite is not a SkinComposite, no layout should be done + return; + } + + boolean canProceed = true; + + if (flushCache || (mainDisplayChild == null) || (cliDisplayChild == null)) + { + Control[] children = composite.getChildren(); + canProceed = checkChidren(children); + } + + if (canProceed) + { + // Retrieve needed data from composite + SkinComposite skinComposite = (SkinComposite) composite; + IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(composite); + boolean flipSlideClosed = false; + if (instance != null) + { + //flipSlideClosed = instance.isFlipSlideClosed(); + flipSlideClosed = false; + } + + skinComposite.recalculateZoomFactor(); + skinComposite.updateDisplayRectangle(); + + double zoomFactor = skinComposite.getZoomFactor(); + Rectangle displayRectangle = skinComposite.getDisplayRectangle(); + + // Handling main display case + if (mainDisplayChild != null) + { + layoutMainDisplay(zoomFactor, displayRectangle); + + if ((isFlipSupported) && (flipSlideClosed)) + { + // On phones that support flip, the main display is hidden + // when the flip is closed + mainDisplayChild.setVisible(false); + } + else + { + mainDisplayChild.setVisible(true); + } + } + + // Handling CLI display case + if (cliDisplayChild != null) + { + layoutCliDisplay(zoomFactor, displayRectangle, flipSlideClosed); + + if (((externalDisplayAvailable) && (!flipSlideClosed)) + || ((openExternalDisplayAvailable) && (flipSlideClosed))) + { + // The CLI display is shown in 2 situations: + // 1. When the flip is closed and there is information about + // external display + // 2. When the flip is opened and there is information about + // open external display + cliDisplayChild.setVisible(true); + } + else + { + cliDisplayChild.setVisible(false); + } + } + } + + } + + /** + * Checks if the composite children are as expected by the layout + * It is needed to check if the composite's children are, at most: + * 1. One main display + * 2. One CLI display + * + * @param children Array of composite children to check + * + * @return true if children are as expected; false otherwise + */ + private boolean checkChidren(Control[] children) + { + + RemoteCLIDisplay cliDisplayInstance = null; + SWTRemoteDisplay mainDisplayInstance = null; + boolean childrenOk = true; + + // We need to check if the composite's children are, at most: + // + // 1. One main display + // 2. One CLI display + for (Control child : children) + { + if (child instanceof SWTRemoteDisplay) + { + if (mainDisplayInstance == null) + { + mainDisplayInstance = (SWTRemoteDisplay) child; + mainDisplayChild = mainDisplayInstance; + } + else + { + childrenOk = false; + + break; + } + } + else if (child instanceof RemoteCLIDisplay) + { + if (cliDisplayInstance == null) + { + cliDisplayInstance = (RemoteCLIDisplay) child; + cliDisplayChild = cliDisplayInstance; + } + else + { + childrenOk = false; + + break; + } + } + else + { + childrenOk = false; + + break; + } + } + + return childrenOk; + } + + /** + * Performs the layout for main display + * + * @param zoomFactor The zoom factor to use when dimensioning the main display. + * @param displayRectangle The area of skin that is being shown at the view. It can + * be only a part of the skin if the view client area + * is smaller than the skin size. It is used to calculate translation. + */ + private void layoutMainDisplay(double zoomFactor, Rectangle displayRectangle) + { + // Computes main display position + int x = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_X) * zoomFactor) + - displayRectangle.x; + int y = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_Y) * zoomFactor) + - displayRectangle.y; + int width = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH) + * zoomFactor * skin.getEmbeddedViewScale()); + int height = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT) + * zoomFactor * skin.getEmbeddedViewScale()); + + // Sets main display size and position + mainDisplayChild.setZoomFactor(zoomFactor * skin.getEmbeddedViewScale()); + mainDisplayChild.setBounds(x, y, width, height); + } + + /** + * Performs the layout for CLI display + * + * @param zoomFactor The zoom factor to use when dimensioning the main display + * @param displayRectangle The area of skin that is being shown at the view. It can + * be only a part of the skin if the view client area + * is smaller than the skin size. It is used to calculate translation. + * @param isFlipSlideClosed True if the flip or slide is currently closed. False otherwise. + */ + private void layoutCliDisplay(double zoomFactor, Rectangle displayRectangle, + boolean flipSlideClosed) + { + // Computes CLI display position + int x = 0; + int y = 0; + int width = 0; + int height = 0; + + if (!flipSlideClosed && openExternalDisplayAvailable) + { + x = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_X) * zoomFactor) + - displayRectangle.x; + y = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_Y) * zoomFactor) + - displayRectangle.y; + width = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_WIDTH) + * zoomFactor * IAndroidUIConstants.DISPLAY_TO_SKIN_RATIO); + height = + (int) (skin + .getSkinPropertyValue(ISkinKeyXmlTags.SKIN_OPEN_EXTERNAL_VIEW_HEIGHT) + * zoomFactor * IAndroidUIConstants.DISPLAY_TO_SKIN_RATIO); + } + else if (flipSlideClosed && externalDisplayAvailable) + { + x = + (int) ((skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_X) * zoomFactor) - displayRectangle.x); + y = + (int) ((skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_Y) * zoomFactor) - displayRectangle.y); + width = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_WIDTH) + * zoomFactor * IAndroidUIConstants.DISPLAY_TO_SKIN_RATIO); + height = + (int) (skin.getSkinPropertyValue(ISkinKeyXmlTags.SKIN_EXTERNAL_VIEW_HEIGHT) + * zoomFactor * IAndroidUIConstants.DISPLAY_TO_SKIN_RATIO); + } + + // Sets cli display size + cliDisplayChild.setZoomFactor(zoomFactor * IAndroidUIConstants.DISPLAY_TO_SKIN_RATIO); + cliDisplayChild.setBounds(x, y, width, height); + } + + void dispose() + { + cliDisplayChild = null; + mainDisplayChild = null; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/skin/SkinComposite.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/skin/SkinComposite.java new file mode 100644 index 0000000..fa50267 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/controls/skin/SkinComposite.java @@ -0,0 +1,1300 @@ +/* +* 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.ui.controls.skin; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.Collection; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackAdapter; +import org.eclipse.swt.events.MouseWheelListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.ui.PlatformUI; + +import com.motorola.studio.android.adt.DDMSFacade; +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.exception.InstanceStopException; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.model.IInputLogic; +import com.motorola.studio.android.emulator.core.skin.AndroidPressKey; +import com.motorola.studio.android.emulator.core.skin.AndroidSkinBean; +import com.motorola.studio.android.emulator.core.skin.IAndroidKey; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.AndroidLogicUtils; +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.emulator.ui.controls.UIHelper; +import com.motorola.studio.android.emulator.ui.handlers.IHandlerConstants; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * DESCRIPTION: This class implements the UI part of the skin + * + * RESPONSIBILITY: - Provide the skin image with correct layout - Provide means + * for the user to send events to the emulator through the phone emulated + * keyboard - Provide means for the user to change the zoom and window scrolling + * properties + * + * COLABORATORS: None. + * + * USAGE: Create an instance of this class every time an phone instance is to be + * displayed Call the public methods to interact with the skin. + */ +public class SkinComposite extends Composite implements IAndroidComposite +{ + // Constants for defining interval between keystrokes + // when using long mouse click + public static final int FIRST_REFRESH_DELAY_MS = 300; + + public static final int REFRESH_DELAY_PERIOD_MS = 100; + + // Minimum value to be used as zoom factor. This is necessary to avoid + // divisions to zero + private static final double MINIMUM_ZOOM_FACTOR = 0.0001; + + // The step increased or decreased from the zoom factor using mouse wheel + private static final double ZOOM_STEP = 0.5; + + /** + * A rectangle that represents the part of the currentSkin image that fits + * into the view/screen. It must fit inside the currentSkinImage borders (0, + * 0, current skin image width, current skin image height) + */ + private final Rectangle displayRectangle = new Rectangle(0, 0, 0, 0); + + /** + * The skin elements provider + */ + private IAndroidSkin skin; + + /** + * The image that is currently drawn at screen. It is one image provided by + * the skin that is scaled by the current zoom factor + */ + private Image currentSkinImage; + + /** + * The key that mouse is over at the current moment + */ + private IAndroidKey currentKey; + + /** + * The flag indicating that Ctrl key is pressed + */ + private boolean ctrlPressed = false; + + /** + * SWT key pressed/released events listener + */ + private KeyListener keyListener; + + private MouseListener mainDisplayMouseListener; + + private MouseMoveListener mainDisplayMouseMoveListener; + + /** + * Flag that indicates if the skin can use the scroll bars to draw itself + * bigger than the view client area + */ + private boolean scrollBarsUsed = true; + + /** + * True if the mouse left button is pressed. False otherwise + */ + private boolean isMouseLeftButtonPressed; + + /** + * True if the mouse right button is pressed. False otherwise + */ + private boolean isMouseRightButtonPressed; + + /** + * The zoom factor whose default value is 1.0 (100%) + */ + private double zoomFactor = 1.0; + + private double embeddedViewScale = 1.0; + + private IInputLogic androidInput; + + private boolean isMouseMainDisplayLeftButtonPressed; + + IAndroidEmulatorInstance androidInstance; + + /** + * Creates a SkinComposite This composite holds the screens in the correct + * positions and maps the keys + * + * @param parent + * The parent composite in which the UI part of the instance + * shall be created + * @param androidSkin + * The skin object that contain data for getting skin information + */ + public SkinComposite(Composite parent, IAndroidSkin androidSkin, + IAndroidEmulatorInstance instance) + { + super(parent, SWT.BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL); + skin = androidSkin; + androidInstance = instance; + + // Add listeners + addListeners(); + createListenersForMainDisplay(); + + setToolTipText(null); + + androidInput = instance.getInputLogic(); + + // Init the scroll bars + initScrollBars(); + + if (!Platform.getOS().equals(Platform.OS_MACOSX)) + { + hideEmulatorWindow(); + } + } + + private void hideEmulatorWindow() + { + int port = + AndroidLogicUtils.getEmulatorPort(DDMSFacade.getSerialNumberByName(androidInstance + .getName())); + long windowHandle = NativeUIUtils.getWindowHandle(androidInstance.getName(), port); + androidInstance.setWindowHandle(windowHandle); + NativeUIUtils.hideWindow(windowHandle); + } + + /** + * Add listeners to the skin composite + */ + private void addListeners() + { + + addPaintListener(new PaintListener() + { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent) + */ + public void paintControl(PaintEvent e) + { + // This listener is invoked when a regular SWT redraw is invoked. In this case, no keys + // have changed. That's why we pass "null" as parameter + drawSkin(e.gc, null); + } + }); + + addMouseListener(new MouseAdapter() + { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.MouseAdapter#mouseUp(org.eclipse.swt.events.MouseEvent) + */ + @Override + public void mouseUp(MouseEvent e) + { + if (e.button == 1) + { + isMouseLeftButtonPressed = false; + + } + else if (e.button == 3) + { + isMouseRightButtonPressed = false; + } + + // Handle left button mouse up event + if (e.button == 1) + { + cancelMouseSelection(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.MouseAdapter#mouseDown(org.eclipse.swt.events.MouseEvent) + */ + @Override + public void mouseDown(MouseEvent e) + { + setFocus(); + if (e.button == 1) + { + isMouseLeftButtonPressed = true; + } + else if (e.button == 3) + { + isMouseRightButtonPressed = true; + } + + if (currentKey != null) + { + ImageData mergedImage = getKeyImageData(currentKey, false); + setSkinImage(mergedImage, currentKey, true); + + // Handle left button mouse down event + if ((e.button == 1) && (!isMouseRightButtonPressed) && (currentKey != null)) + { + androidInput.sendClick(currentKey.getKeysym(), true); + } + + // Handle right button mouse down event + else if (e.button == 3) + { + cancelMouseSelection(); + } + + } + } + }); + + addMouseMoveListener(new MouseMoveListener() + { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent) + */ + public void mouseMove(MouseEvent e) + { + int posX = (int) ((e.x + displayRectangle.x) / zoomFactor); + int posY = (int) ((e.y + displayRectangle.y) / zoomFactor); + IAndroidKey keyData = getSkinKey(posX, posY); + + if ((isMouseLeftButtonPressed) && (keyData != currentKey)) + { + cancelMouseSelection(); + } + + if (!isMouseLeftButtonPressed && (currentKey != keyData)) + { + changeCurrentKey(keyData); + } + } + }); + + // listener to change the zoom factor using Ctrl + Mouse Wheel + addMouseWheelListener(new MouseWheelListener() + { + public void mouseScrolled(MouseEvent event) + { + if (ctrlPressed) + { + + // the floor and ceil are required if "fits to window" was + // checked. + double roundedZoomFactor = Math.floor(zoomFactor / ZOOM_STEP) * ZOOM_STEP; + + if ((event.count > 0) && (roundedZoomFactor < IHandlerConstants.MAXIMUM_ZOOM)) + { + // increase zoom factor + setZoomFactor(roundedZoomFactor + ZOOM_STEP); + applyZoomFactor(); + } + + else if ((event.count < 0) + && (roundedZoomFactor > IHandlerConstants.MINIMUM_ZOOM)) + { + // decrease zoom factor + setZoomFactor(roundedZoomFactor - ZOOM_STEP); + applyZoomFactor(); + } + + } + } + }); + + addMouseTrackListener(new MouseTrackAdapter() + { + @Override + public void mouseExit(MouseEvent mouseevent) + { + changeCurrentKey(null); + } + }); + + addControlListener(new ControlAdapter() + { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent) + */ + @Override + public void controlResized(ControlEvent event) + { + if (scrollBarsUsed) + { + synchronizeScrollBars(); + } + else + { + ImageData imageData = getImageData(false, false); + setSkinImage(imageData, null, false); + } + } + }); + + } + + public void applyLayout(String layoutName) + { + // Populate the attributes with information from skin + AndroidSkinBean skinBean = null; + + try + { + skinBean = skin.getSkinBean(layoutName); + } + catch (SkinException e) + { + error("The skin data could not be retrieved from skin files. Cause: " + e.getMessage()); + EclipseUtils.showErrorDialog(e); + } + + // Create layout and set it to composite + if (skinBean != null) + { + // When changing to a new layout, the key may move to another position + // It does not make sense to keep the old key object + currentKey = null; + + // Change the background color to the one that applies to the layout being set + RGB color = skin.getBackgroundColor(layoutName); + setBackground(new Color(PlatformUI.getWorkbench().getDisplay(), color)); + + Layout prevLayout = getLayout(); + if (prevLayout instanceof AndroidSkinLayout) + { + ((AndroidSkinLayout) prevLayout).dispose(); + } + + AndroidSkinLayout androidLayout = + new AndroidSkinLayout(skinBean, skin.isFlipSupported()); + setLayout(androidLayout); + + embeddedViewScale = skinBean.getEmbeddedViewScale(); + + layout(); + redraw(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.widgets.Widget#dispose() + */ + @Override + public void dispose() + { + if (androidInput != null) + { + androidInput.dispose(); + } + + if (currentSkinImage != null) + { + currentSkinImage.dispose(); + } + + Layout layout = getLayout(); + if (layout instanceof AndroidSkinLayout) + { + ((AndroidSkinLayout) layout).dispose(); + } + + skin = null; + currentKey = null; + keyListener = null; + mainDisplayMouseListener = null; + mainDisplayMouseMoveListener = null; + + if (!Platform.getOS().equals(Platform.OS_MACOSX)) + { + long hnd = androidInstance.getWindowHandle(); + if (hnd > 0) + { + NativeUIUtils.showWindow(hnd); + NativeUIUtils.restoreWindow(hnd); + } + + //Force update on redrawing + androidInstance.setWindowHandle(0); + } + + super.dispose(); + } + + /** + * Sets the zoom factor to use in the instance + */ + public void applyZoomFactor() + { + if (getZoomFactor() == 0) + { + scrollBarsUsed = false; + getVerticalBar().setEnabled(false); + getHorizontalBar().setEnabled(false); + } + else + { + scrollBarsUsed = true; + getVerticalBar().setEnabled(true); + getHorizontalBar().setEnabled(true); + } + + // Resets translation + displayRectangle.x = 0; + displayRectangle.y = 0; + + redrawReleasedImage(); + + } + + /** + * Sets the flip/slide status of the phone + */ + private void redrawReleasedImage() + { + if (currentSkinImage != null) + { + ImageData imageData = getImageData(false, false); + setSkinImage(imageData, null, false); + layout(); + redraw(); + } + + if (scrollBarsUsed) + { + synchronizeScrollBars(); + } + } + + /** + * Performs the skin draw operation. + * + * @param gcUsedToDraw + * The gc object associated with the skin composite that is used + * to draw the images + */ + private void drawSkin(GC gcUsedToDraw, IAndroidKey changedKey) + { + if (currentSkinImage == null) + { + IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this); + ImageData initialSkinImage = getImageData(false, false); + setSkinImage(initialSkinImage, null, false); + applyLayout(instance.getCurrentLayout()); + + if (scrollBarsUsed) + { + synchronizeScrollBars(); + } + } + + if (displayRectangle != null) + { + int srcXPos, srcYPos, srcWidth, srcHeight; + int destXPos, destYPos, destWidth, destHeight; + if (changedKey == null) + { + srcXPos = displayRectangle.x; + srcYPos = displayRectangle.y; + srcWidth = displayRectangle.width; + srcHeight = displayRectangle.height; + destXPos = 0; + destYPos = 0; + destWidth = Math.min(currentSkinImage.getImageData().width, displayRectangle.width); + destHeight = + Math.min(currentSkinImage.getImageData().height, displayRectangle.height); + } + else + { + srcXPos = + ((int) (changedKey.getKeyArea().x > 0 ? changedKey.getKeyArea().x * zoomFactor + : 0)); + srcYPos = + ((int) (changedKey.getKeyArea().y > 0 ? changedKey.getKeyArea().y * zoomFactor + : 0)); + srcWidth = ((int) (changedKey.getKeyArea().width * zoomFactor)); + srcHeight = ((int) (changedKey.getKeyArea().height * zoomFactor)); + destXPos = srcXPos - displayRectangle.x; + destYPos = srcYPos - displayRectangle.y; + destWidth = srcWidth; + destHeight = srcHeight; + } + + gcUsedToDraw.drawImage(currentSkinImage, srcXPos, srcYPos, srcWidth, srcHeight, + destXPos, destYPos, destWidth, destHeight); + } + } + + /** + * Loads a new screen image to the currentSkin attribute. This action + * updates the skin image that is drawn as skin + * + * @param imageToSet + * The new skin pixel data, as retrieved from the skin plugin + * @param changedKey + * The key that has changed, if any. If one if provided, only the key area should be redrawn. + * Can be <code>null</code>. + * @param forceDraw + * true if a draw is needed after setting the new image; false if replacing skin image only + * will be performed. + */ + private void setSkinImage(ImageData imageToSet, IAndroidKey changedKey, boolean forceDraw) + { + + recalculateZoomFactor(); + + if (imageToSet != null) + { + if (currentSkinImage != null) + { + currentSkinImage.dispose(); + } + + // Scales the chosen image and sets to currentSkin attribute + // + // NOTE: width and height cannot be equal to MINIMUM_ZOOM_FACTOR, + // because this + // will raise an IllegalArgumentException when constructing the + // Image object + int width = + (zoomFactor == MINIMUM_ZOOM_FACTOR ? 1 : (int) (imageToSet.width * zoomFactor)); + int height = + (zoomFactor == MINIMUM_ZOOM_FACTOR ? 1 : (int) (imageToSet.height * zoomFactor)); + currentSkinImage = new Image(getDisplay(), imageToSet.scaledTo(width, height)); + + // It only makes sense to reset the translation if the skin image is really being changed + // It will happen if we set a image data without specifying a changed key + if (changedKey == null) + { + displayRectangle.x = 0; + displayRectangle.y = 0; + } + + if (forceDraw) + { + layout(); + + GC gc = new GC(this); + drawSkin(gc, changedKey); + gc.dispose(); + } + } + else + { + info("It was requested to set a skin image that was null. Operation aborted."); + } + } + + /** + * This method is responsible to set the scroll bar attributes so that they + * reflect the size of the current image at the current zoom factor + */ + private void synchronizeScrollBars() + { + // Syncronizing only makes sense if there is a skin being drawn + if (currentSkinImage != null) + { + + // Retrieves the current image and client area sizes + Rectangle imageBound = currentSkinImage.getBounds(); + int cw = getClientArea().width; + int ch = getClientArea().height; + + // Updates horizontal scroll bar attributes + ScrollBar horizontal = getHorizontalBar(); + horizontal.setIncrement((cw / 100)); + horizontal.setPageIncrement((cw / 2)); + horizontal.setMaximum(imageBound.width); + horizontal.setThumb(cw); + horizontal.setSelection(displayRectangle.x); + + // Updates vertical scroll bar attributes + ScrollBar vertical = getVerticalBar(); + vertical.setIncrement((ch / 100)); + vertical.setPageIncrement((ch / 2)); + vertical.setMaximum(imageBound.height); + vertical.setThumb(ch); + vertical.setSelection(displayRectangle.y); + + if (horizontal.getMaximum() > cw) // Image is wider than client area + { + horizontal.setEnabled(true); + } + else + { + horizontal.setEnabled(false); + } + + if (vertical.getMaximum() > ch) // Image is wider than client area + { + vertical.setEnabled(true); + } + else + { + vertical.setEnabled(false); + } + + } + } + + /** + * Initialize the scroll bars This include: a) setting the initial enabled + * state b) adding the necessary listeners + */ + private void initScrollBars() + { + + ScrollBar horizontal = getHorizontalBar(); + horizontal.setEnabled(false); + horizontal.addSelectionListener(new SelectionAdapter() + { + /** + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent event) + { + // Updates the translation + displayRectangle.x = ((ScrollBar) event.widget).getSelection(); + + // Update the UI + layout(); + redraw(); + } + }); + + ScrollBar vertical = getVerticalBar(); + vertical.setEnabled(false); + vertical.addSelectionListener(new SelectionAdapter() + { + /** + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent event) + { + // Updates the translation + displayRectangle.y = ((ScrollBar) event.widget).getSelection(); + + // Update the UI + layout(); + redraw(); + } + }); + + debug("Initialized scroll bars"); + } + + /** + * This method retrieves the key that is placed at the given x,y + * coordinates, considering the flip status + * + * @param x + * The X coordinate to use at key lookup + * @param y + * The Y coordinate to use at key lookup + * + * @return The key placed at the given coordinate, or null if none is found + */ + private IAndroidKey getSkinKey(int x, int y) + { + IAndroidKey keyToReturn = null; + IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this); + + Collection<IAndroidKey> keyAreas = skin.getKeyDataCollection(instance.getCurrentLayout()); + if (keyAreas != null) + { + for (IAndroidKey key : keyAreas) + { + if (key.isInsideKey(x, y)) + { + if (key instanceof AndroidPressKey) + { + AndroidPressKey defaultKeyData = (AndroidPressKey) key; + + //if (!defaultKeyData.isFlipSlideValid(instance.isFlipSlideClosed())) + if (!defaultKeyData.isFlipSlideValid(false)) + { + continue; + } + + } + + keyToReturn = key; + break; + } + } + } + + return keyToReturn; + } + + /** + * Retrieves an image data. If it has never been used at the current + * session, loads it from skin. Released image is retrieved if both parameters + * are false. + * + * @param isPressed + * true if the image to be retrieved is the pressed image + * @param isEnter + * true if the image to be retrieved is the enter image. It only has effect if + * isPressed == false + * + * @return An image data containing the desired image pixels + */ + private ImageData getImageData(boolean isPressed, boolean isEnter) + { + + ImageData imageData = null; + + IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this); + + try + { + if (isPressed) + { + imageData = skin.getPressedImageData(instance.getCurrentLayout()); + + } + else + { + if (isEnter) + { + imageData = skin.getEnterImageData(instance.getCurrentLayout()); + + } + else + { + imageData = skin.getReleasedImageData(instance.getCurrentLayout()); + + } + } + } + catch (SkinException e) + { + error("The image requested from skin could not be retrieved. isPressed=" + isPressed + + "; message=" + e.getMessage()); + EclipseUtils.showErrorDialog(e); + error("The skin could not provide an important resource. Stopping the instance"); + try + { + instance.stop(true); + } + catch (InstanceStopException e1) + { + error("Error while running service for stopping virtual machine"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_General_CannotRunStopService); + } + } + + return imageData; + } + + /** + * Builds an image data that is based on the released image but has the + * pressed/enter key area painted with data from the pressed/enter image + * + * @param key + * The pressed key + * @param isEnter + * Whether the image being retrieved will be used for enter or pressed + * + * @return An image data built the way described at method description + */ + private ImageData getKeyImageData(IAndroidKey key, boolean isEnter) + { + + ImageData resultingImage; + ImageData releasedImage = getImageData(false, false); + ImageData keyImage = isEnter ? getImageData(false, true) : getImageData(true, false); + + resultingImage = (ImageData) releasedImage.clone(); + + Rectangle keyArea = key.getKeyArea(); + int resultingImageSize = resultingImage.width * keyArea.height; + int[] keyPixelBuffer = new int[resultingImageSize]; + + int startY = keyArea.y < 0 ? 0 : keyArea.y; + + keyImage.getPixels(0, startY, resultingImageSize, keyPixelBuffer, 0); + + for (int line = 0; line < keyArea.height; line++) + { + int pos = (line * resultingImage.width) + Math.abs(keyArea.x); + int startX = Math.abs(keyArea.x); + startY = (keyArea.y < 0 ? 0 : keyArea.y) + line; + if (startY < 0) + { + continue; + } + + int putWidth = keyArea.x < 0 ? keyArea.width - startX : keyArea.width; + resultingImage.setPixels(startX, startY, putWidth, keyPixelBuffer, pos); + } + + return resultingImage; + } + + /** + * This method is called when a mouse selection needs to be canceled. + * Pressing the right button, releasing the left button or leaving the key + * area are examples of typical conditions. + */ + private void cancelMouseSelection() + { + // If the mouse timer is different from null, that means that a key is + // pressed + // This check is important so that event messages are not sent by + // mistake + + if (currentKey != null) + { + + ImageData newImageData = getImageData(false, true); + setSkinImage(newImageData, currentKey, true); + androidInput.sendClick(currentKey.getKeysym(), false); + } + } + + private void changeCurrentKey(IAndroidKey newKey) + { + // The following actions are executed only if the key has changed since the last + // time a mouse move event has happened. + ImageData newImage; + + if (currentKey != null) + { + // If currentKey is different from null, we know that the mouse cursor has + // left the area defined by currentKey. That is because from the previous + // if clause we know that the key has changed, and currentKey attribute + // state is out-dated until we reach the "currentKey = keyData" statement. + // In this case, we must draw the RELEASED image version of the key at the + // key location + newImage = getImageData(false, false); + setSkinImage(newImage, currentKey, true); + setToolTipText(null); + } + + if (newKey != null) + { + // If keyData is different from null, we know that the mouse cursor has + // entered the area defined by keyData. + // In this case, we must draw the ENTER image version of the key at the + // key location + newImage = getKeyImageData(newKey, true); + setSkinImage(newImage, newKey, true); + setToolTipText(newKey.getToolTip()); + } + currentKey = newKey; + } + + /** + * Retrieves the display rectangle defined at the current moment. + * + * @return The display rectangle + */ + Rectangle getDisplayRectangle() + { + return displayRectangle; + } + + /** + * Updates the size and location of the display rectangle, based on the + * current attributes of the skin (such as skin size and zoom factor) and + * view size + */ + void updateDisplayRectangle() + { + // Updating the display rectangle only makes sense if we have a skin + // being drawn + if (currentSkinImage != null) + { + + // Collect the variables used in computation + // + // - clientAreaWidth, clientAreaHeight: dimensions of the view, + // measured from + // the upper left corner point (0,0) to the lower right corner point + // (width, height) + // - currentSkinWidth, currentSkinHeight: dimensions of the skin + // picture, already scaled + // by the zoom factor + int clientAreaWidth = getClientArea().width; + int clientAreaHeight = getClientArea().height; + int currentSkinHeight = currentSkinImage.getImageData().height; + int currentSkinWidth = currentSkinImage.getImageData().width; + + // Updates the display rectangle y and height + // + // FIRST STEP: determine the position of the rectangle's y + // coordinate. + // - It starts by calculating if there is any blank area at the + // bottom + // of the view. + // - If there is blank space (blankY > 0) then calculate which + // point, + // if set as the display rectangle's y coordinate, would make the + // blank + // space to disappear. Store this as a candidate Y. + // - Check if the candidate Y is valid. If not valid (candidateY < + // 0) + // then use the image origin as the final y coordinate + // + // SECOND STEP: determine the width dimension of the rectangle + // - It starts by calculating which would be the coordinate of the + // lower point, assuming that the image is big enough to contain + // that + // coordinate. That value (vEdge) is the sum of the Y coordinate and + // the view height. + // - If vEdge is bigger than the current view height, that means + // that the image will occupy part of the view. Itis necessary to + // make the rectangle fit in the skin image by making it smaller in + // height than the view height itself. + // - If vEdge is smaller than the current view height, that means + // that + // the image will not fit in the view. The solution is to make the + // display rectangle height the same size as the view height. In + // this + // second case, the display rectangle will fit in the view height, + // but + // will not be bigger than the skin image height itself + int blankY = clientAreaHeight - (currentSkinHeight - displayRectangle.y); + if (blankY > 0) + { + int candidateY = displayRectangle.y - blankY; + if (candidateY > 0) + { + displayRectangle.y = candidateY; + } + else + { + displayRectangle.y = 0; + } + } + int vEdge = displayRectangle.y + clientAreaHeight; + if (vEdge > currentSkinHeight) + { + displayRectangle.height = currentSkinHeight - displayRectangle.y; + } + else + { + displayRectangle.height = clientAreaHeight; + } + + // Updates the display rectangle x and width + // NOTE: a similar logic to the previous one was applied in this + // case + int blankX = clientAreaWidth - (currentSkinWidth - displayRectangle.x); + if (blankX > 0) + { + int candidateX = displayRectangle.x - blankX; + if (candidateX > 0) + { + displayRectangle.x = candidateX; + } + else + { + displayRectangle.x = 0; + } + } + int hEdge = displayRectangle.x + clientAreaWidth; + if (hEdge > currentSkinWidth) + { + displayRectangle.width = currentSkinWidth - displayRectangle.x; + } + else + { + displayRectangle.width = clientAreaWidth; + } + + } + } + + /** + * Recalculates the zoom factor. This is necessary when the screen or image + * dimensions change. + */ + void recalculateZoomFactor() + { + + if (!scrollBarsUsed) + { + // Compute new zoom factor if the zoom mode is "Fit to Window" + Rectangle clientArea = getClientArea(); + if ((clientArea.width == 0) || (clientArea.height == 0)) + { + // zoom factor cannot be zero, otherwise an + // IllegalArgumentException + // is raised in some SWT methods + setZoomFactor(MINIMUM_ZOOM_FACTOR); + } + else + { + ImageData currentSkin = getImageData(false, false); + double widthRatio = (double) (clientArea.width) / currentSkin.width; + double heightRatio = (double) (clientArea.height) / currentSkin.height; + setZoomFactor(Math.min(widthRatio, heightRatio)); + } + } + + } + + /** + * Gets the current zoom factor. + * + * @return the zoom factor + */ + public double getZoomFactor() + { + return zoomFactor; + } + + /** + * Checks if the current zoom configuration is "fit to screen"; + * @return + */ + public boolean isFitToWindowSelected() + { + return !scrollBarsUsed; + } + + /** + * Sets the zoom factor. + * + * @param zoom + * the zoom factor + */ + public void setZoomFactor(double zoom) + { + zoomFactor = zoom; + } + + /** + * Gets the listener that handles SWT key pressing and releasing events. + * + * @return the KeyListener object + */ + public KeyListener getKeyListener() + { + return keyListener; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.ui.controls.IAndroidComposite#getMouseListener() + */ + public MouseListener getMouseListener() + { + return mainDisplayMouseListener; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.ui.controls.IAndroidComposite#getMouseMoveListener() + */ + public MouseMoveListener getMouseMoveListener() + { + return mainDisplayMouseMoveListener; + } + + private void createListenersForMainDisplay() + { + // create listener to handle keyboard key pressing highlighting the key + // in the skin. + keyListener = new KeyAdapter() + { + + @Override + public void keyPressed(KeyEvent arg0) + { + + int keyCode = arg0.keyCode; + + if (keyCode == SWT.CTRL) + { + ctrlPressed = true; + } + else + { + if (keyCode == SWT.ARROW_DOWN || keyCode == SWT.ARROW_LEFT + || keyCode == SWT.ARROW_RIGHT || keyCode == SWT.ARROW_UP) + { + int dpadRotation = skin.getDpadRotation(androidInstance.getCurrentLayout()); + keyCode = getRotatedKeyCode(keyCode, dpadRotation); + } + // send message to emulator + androidInput.sendKey(arg0.character, keyCode, skin.getKeyCodes()); + } + + } + + @Override + public void keyReleased(KeyEvent arg0) + { + int keyCode = arg0.keyCode; + + if (keyCode == SWT.CTRL) + { + ctrlPressed = false; + } + } + + }; + + mainDisplayMouseMoveListener = new MouseMoveListener() + { + /** + * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(MouseEvent) + */ + public void mouseMove(MouseEvent e) + { + if (isMouseMainDisplayLeftButtonPressed) + { + UIHelper.ajustCoordinates(e, SkinComposite.this); + androidInput.sendMouseMove((int) (e.x / embeddedViewScale), + (int) (e.y / embeddedViewScale)); + } + } + }; + + mainDisplayMouseListener = new MouseAdapter() + { + /** + * @see org.eclipse.swt.events.MouseListener#mouseUp(MouseEvent) + */ + @Override + public void mouseUp(MouseEvent e) + { + if (e.button == 1) + { + isMouseMainDisplayLeftButtonPressed = false; + + UIHelper.ajustCoordinates(e, SkinComposite.this); + androidInput.sendMouseUp((int) (e.x / embeddedViewScale), + (int) (e.y / embeddedViewScale)); + } + } + + /** + * @see org.eclipse.swt.events.MouseListener#mouseDown(MouseEvent) + */ + @Override + public void mouseDown(MouseEvent e) + { + if (e.button == 1) + { + UIHelper.ajustCoordinates(e, SkinComposite.this); + androidInput.sendMouseDown((int) (e.x / embeddedViewScale), + (int) (e.y / embeddedViewScale)); + + isMouseMainDisplayLeftButtonPressed = true; + } + } + }; + } + + private int getRotatedKeyCode(int keyCode, int dpadRotation) + { + switch (dpadRotation % 4) + { + case 1: + switch (keyCode) + { + case SWT.ARROW_DOWN: + keyCode = SWT.ARROW_RIGHT; + break; + case SWT.ARROW_LEFT: + keyCode = SWT.ARROW_DOWN; + break; + case SWT.ARROW_RIGHT: + keyCode = SWT.ARROW_UP; + break; + case SWT.ARROW_UP: + keyCode = SWT.ARROW_LEFT; + break; + } + break; + case 2: + switch (keyCode) + { + case SWT.ARROW_DOWN: + keyCode = SWT.ARROW_UP; + break; + case SWT.ARROW_LEFT: + keyCode = SWT.ARROW_RIGHT; + break; + case SWT.ARROW_RIGHT: + keyCode = SWT.ARROW_LEFT; + break; + case SWT.ARROW_UP: + keyCode = SWT.ARROW_DOWN; + break; + } + break; + case 3: + switch (keyCode) + { + case SWT.ARROW_DOWN: + keyCode = SWT.ARROW_LEFT; + break; + case SWT.ARROW_LEFT: + keyCode = SWT.ARROW_UP; + break; + case SWT.ARROW_RIGHT: + keyCode = SWT.ARROW_DOWN; + break; + case SWT.ARROW_UP: + keyCode = SWT.ARROW_RIGHT; + break; + } + break; + default: + //Does nothing, no rotation needed. + break; + } + return keyCode; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/AbstractZoomHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/AbstractZoomHandler.java new file mode 100644 index 0000000..f6a49a9 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/AbstractZoomHandler.java @@ -0,0 +1,146 @@ +/* +* 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.ui.handlers; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.Map; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.commands.IElementUpdater; +import org.eclipse.ui.menus.UIElement; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; +import com.motorola.studio.android.emulator.ui.view.AndroidViewData; + +/** + * DESCRIPTION: + * This class is a handler for the zoom actions + * + * RESPONSIBILITY: + * Execute the zoom operations on demand + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by Eclipse only + */ +public abstract class AbstractZoomHandler extends AbstractHandler implements IHandlerConstants, + IElementUpdater +{ + /** + * @see org.eclipse.core.commands.IHandler#execute(ExecutionEvent) + */ + public Object execute(ExecutionEvent event) throws ExecutionException + { + String viewId = event.getParameter(ACTIVE_VIEW_PARAMETER); + double zoomFactor = getZoomFactor(event.getParameters()); + + info("Setting zoom factor for " + viewId + " = " + zoomFactor); + + IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + if (instance != null) + { + IViewPart viewPart = EclipseUtils.getActiveView(viewId); + if (viewPart instanceof AbstractAndroidView) + { + AbstractAndroidView view = (AbstractAndroidView) viewPart; + if (view.getZoomFactor(instance) != zoomFactor) + { + view.setZoomFactor(instance, zoomFactor); + view.updateActiveViewer(); + } + } + } + else + { + error("The host being presented at the viewer is not available"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_AbstractZoomHandler_InstanceNotFound); + } + return null; + } + + /** + * When a different emulator tab have focus, then each of the zoom factor menu items need + * to update itself so that the radio selection continues at the correct place + * + * @see org.eclipse.ui.commands.IElementUpdater#updateElement(UIElement, Map) + */ + @SuppressWarnings("rawtypes") + public void updateElement(UIElement element, Map parameters) + { + boolean checked = false; + IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + String viewId = (String) parameters.get(ACTIVE_VIEW_PARAMETER); + + if ((instance != null) && (viewId != null)) + { + IViewPart viewPart = EclipseUtils.getActiveView(viewId); + if (viewPart instanceof AbstractAndroidView) + { + AbstractAndroidView view = (AbstractAndroidView) viewPart; + AndroidViewData viewData = view.getViewData(instance); + + double actualZoomFactor = view.getZoomFactor(instance); + if (testZoomFactor(viewData, parameters, actualZoomFactor)) + { + checked = true; + } + } + } + + element.setChecked(checked); + } + + /** + * The zoom actions are enabled when a phone skin is being presented only + * + * @see org.eclipse.core.commands.IHandler#isEnabled() + */ + @Override + public boolean isEnabled() + { + return AbstractAndroidView.getActiveInstance() != null; + } + + /** + * Retrieves the zoom factor associated to this zoom handler instance + * + * @return The zoom factor, as defined in IEmulatorActionConstants class + */ + @SuppressWarnings("rawtypes") + protected abstract double getZoomFactor(Map paramters); + + /** + * Tests if the current zoom factor is the one handled by this zoom handler + * + * @param zoomFactor The active instance current zoom factor + * + * @return True if this handler handles the current zoom factor; false otherwise + */ + @SuppressWarnings("rawtypes") + protected abstract boolean testZoomFactor(AndroidViewData viewData, Map parameters, + double zoomFactor); +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ChangeEmulatorOrientationHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ChangeEmulatorOrientationHandler.java new file mode 100644 index 0000000..6f6af4b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ChangeEmulatorOrientationHandler.java @@ -0,0 +1,119 @@ +/* +* 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.ui.handlers; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.ui.IViewPart; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; +import com.motorola.studio.android.emulator.ui.view.AndroidView; +import com.motorola.studio.android.nativeos.IDevicePropertiesOSConstants; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * This class executes the operation of setting an Emulator Layout with the Landscape orientation + **/ +public class ChangeEmulatorOrientationHandler extends AbstractHandler +{ + + /** + * Sets a layout with the Landscape orientation for the active Emulator. + */ + public Object execute(ExecutionEvent event) + { + IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + + if (instance != null) + { + String viewId = event.getParameter(IHandlerConstants.ACTIVE_VIEW_PARAMETER); + if ((instance != null) && (viewId != null)) + { + IViewPart viewPart = EclipseUtils.getActiveView(viewId); + if (viewPart instanceof AndroidView) + { + if ((instance.getProperties().getProperty(IDevicePropertiesOSConstants.useVnc, + NativeUIUtils.getDefaultUseVnc())).equals("true")) + { + String nextLayout = + AbstractAndroidView.getPreviousOrNextLayout(viewId, + AbstractAndroidView.LayoutOpp.NEXT); + AbstractAndroidView.changeLayout(nextLayout); + + IAndroidSkin skin = ((AbstractAndroidView) viewPart).getSkin(instance); + String cmd = skin.getLayoutScreenCommand(instance.getCurrentLayout()); + instance.changeOrientation(cmd); + } + else + { + AndroidView androidView = (AndroidView) viewPart; + androidView.changeToNextLayout(); + } + } + } + + } + + return null; + } + + /** + * Determines when the Landscape command can be executed. + */ + @Override + public boolean isEnabled() + { + return AbstractAndroidView.getActiveInstance() != null; + } + + // /** + // * Determines what layout is the current one and checks it + // */ + // @SuppressWarnings("unchecked") + // public void updateElement(UIElement element, Map parameters) + // { + // boolean checked = false; + // + // IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + // String viewId = (String) parameters.get(IHandlerConstants.ACTIVE_VIEW_PARAMETER); + // + // if ((instance != null) && (viewId != null)) + // { + // IViewPart viewPart = EclipseUtils.getActiveView(viewId); + // if (viewPart instanceof AbstractAndroidView) + // { + // AbstractAndroidView view = (AbstractAndroidView) viewPart; + // IAndroidSkin skin = view.getSkin(instance); + // + // if (skin != null) + // { + // String currentLayout = instance.getCurrentLayout(); + // String setLayoutName = + // (String) parameters.get(IHandlerConstants.LAYOUT_TO_SET_PARAMETER); + // if ((setLayoutName != null) && (setLayoutName.equals(currentLayout))) + // { + // checked = true; + // } + // } + // } + // } + // + // element.setChecked(checked); + // } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ChangeZoomHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ChangeZoomHandler.java new file mode 100644 index 0000000..e71c062 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ChangeZoomHandler.java @@ -0,0 +1,80 @@ +/* +* 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.ui.handlers; + +import java.util.Map; + +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.emulator.ui.view.AndroidViewData; + +public class ChangeZoomHandler extends AbstractZoomHandler +{ + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.ui.handlers.AbstractZoomHandler#getZoomFactor(java.util.Map) + */ + @SuppressWarnings("rawtypes") + @Override + protected double getZoomFactor(Map parameter) + { + double zoomFactor = DEFAULT_ZOOM; + String factorString = (String) parameter.get(ZOOM_FACTOR_PARAMETER); + try + { + zoomFactor = Double.parseDouble(factorString); + } + catch (Exception e) + { + // Do nothing + // The parameter can always be parsed + } + return zoomFactor; + } + + /** + * Tests if the current zoom factor is the one handled by this zoom handler + * + * @param zoomFactor The active instance current zoom factor + * + * @return True if this handler handles the current zoom factor; false otherwise + */ + @SuppressWarnings("rawtypes") + @Override + protected boolean testZoomFactor(AndroidViewData viewData, Map parameters, double zoomFactor) + { + boolean testResult = false; + double expectedZoomFactor = getZoomFactor(parameters); + if (expectedZoomFactor == zoomFactor) + { + testResult = true; + } + + if (expectedZoomFactor == ZOOM_FIT) + { + if (viewData != null) + { + IAndroidComposite composite = viewData.getComposite(); + + if (composite.isFitToWindowSelected()) + { + testResult = true; + } + } + } + + return testResult; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/IHandlerConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/IHandlerConstants.java new file mode 100644 index 0000000..9a414fc --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/IHandlerConstants.java @@ -0,0 +1,69 @@ +/* +* 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.ui.handlers; + +/** + * This interface contain constants that are used by the action handlers + */ +public interface IHandlerConstants +{ + /** + * Constant used in plugin.xml file to identify change orientation command + */ + String CHANGE_EMULATOR_ORIENTATION_COMMAND = + "com.motorola.studio.android.emulator.ui.change.layout"; + + /** + * Constant used in plugin.xml file to identify change zoom command + */ + String CHANGE_EMULATOR_ZOOM_COMMAND = "com.motorola.studio.android.emulator.ui.change.zoom"; + + /** + * Parameter that determines to which view the command will be applied + */ + String ACTIVE_VIEW_PARAMETER = "activeViewId"; + + /** + * Parameter to determines the zoom fact to be set in the + */ + String ZOOM_FACTOR_PARAMETER = "zoomFactor"; + + /** + * Parameter to determine the increment/decrement to be applied in the current zoonFactor + */ + String ZOOM_CHANGE_FACTOR_PARAMETER = "zoomChangeFactor"; + + /** + * Parameter to determine the emulator display orientation (next, previous, setlayout) to be set. + */ + String EMULATOR_ORIENTATION_PARAMETER = "emulatorOrientation"; + + /** + * Parameter to determine the layout to be set, if EMULATOR_ORIENTATION_PARAMETER value is setlayout + */ + String LAYOUT_TO_SET_PARAMETER = "layoutToSet"; + + // Zoom constants + double ZOOM_FIT = 0; + + double MINIMUM_ZOOM = 0.25; + + double MAXIMUM_ZOOM = 2.0; + + double DEFAULT_ZOOM = 1.00; + + double STEP_ZOOM = 0.25; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ShowViewHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ShowViewHandler.java new file mode 100644 index 0000000..a2a15e2 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ShowViewHandler.java @@ -0,0 +1,79 @@ +/* +* 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.ui.handlers; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.PartInitException; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.ui.view.AndroidView; +import com.motorola.studio.android.emulator.ui.view.MainDisplayView; + +/** + * DESCRIPTION: + * This class is a handler for the show view actions + * + * RESPONSIBILITY: + * Execute the show view operations on demand + * + * COLABORATORS: + * None. + * + * USAGE: + * This class is intended to be used by Eclipse only + */ +public class ShowViewHandler extends AbstractHandler implements IHandlerConstants +{ + /** + * @see org.eclipse.core.commands.IHandler#execute(ExecutionEvent) + */ + public Object execute(ExecutionEvent event) throws ExecutionException + { + String viewId = event.getParameter(IHandlerConstants.ACTIVE_VIEW_PARAMETER); + + try + { + if (viewId.equals(AndroidView.ANDROID_VIEW_ID)) + { + info("Showing Main Display View by command execution"); + EclipseUtils.showView(MainDisplayView.EMULATOR_MAIN_DISPLAY_VIEW_ID); + } + else if (viewId.equals(MainDisplayView.EMULATOR_MAIN_DISPLAY_VIEW_ID)) + { + info("Showing Android View by command execution"); + EclipseUtils.showView(AndroidView.ANDROID_VIEW_ID); + } + else + { + info("User tried to open an unknown view. Ignoring the action."); + } + } + catch (PartInitException e) + { + error("The views that were requested to be opened are not accessible programatically"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_AndroidView_ViewNotFound); + } + + return null; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ZoomInOutHandler.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ZoomInOutHandler.java new file mode 100644 index 0000000..a0d2df4 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/handlers/ZoomInOutHandler.java @@ -0,0 +1,90 @@ +/* +* 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.ui.handlers; + +import java.util.Map; + +import org.eclipse.ui.IViewPart; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.ui.view.AbstractAndroidView; +import com.motorola.studio.android.emulator.ui.view.AndroidViewData; + +/** + * This class is responsible for increasing the default zoom factor + * to the current viewer of the Main Display View. + */ +public class ZoomInOutHandler extends AbstractZoomHandler +{ + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.ui.handlers.AbstractZoomHandler#getZoomFactor(java.util.Map) + */ + @SuppressWarnings("rawtypes") + @Override + protected double getZoomFactor(Map parameters) + { + double zoomFactor = DEFAULT_ZOOM; + IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + String viewId = (String) parameters.get(ACTIVE_VIEW_PARAMETER); + String changeFactorString = (String) parameters.get(ZOOM_CHANGE_FACTOR_PARAMETER); + + if ((instance != null) && (viewId != null) && (changeFactorString != null)) + { + IViewPart viewPart = EclipseUtils.getActiveView(viewId); + if (viewPart instanceof AbstractAndroidView) + { + AbstractAndroidView view = (AbstractAndroidView) viewPart; + double currentZoomFactor = view.getZoomFactor(instance); + + try + { + double changeZoomFactor = Double.parseDouble(changeFactorString); + zoomFactor = currentZoomFactor + changeZoomFactor; + } + catch (Exception e) + { + zoomFactor = currentZoomFactor; + } + } + } + + if (zoomFactor < MINIMUM_ZOOM) + { + zoomFactor = MINIMUM_ZOOM; + } + else if (zoomFactor > MAXIMUM_ZOOM) + { + zoomFactor = MAXIMUM_ZOOM; + } + + return zoomFactor; + } + + /* + * (non-Javadoc) + * @see com.motorola.studio.android.emulator.ui.handlers.AbstractZoomHandler#testZoomFactor(com.motorola.studio.android.emulator.ui.view.AndroidViewData, java.util.Map, double) + */ + @SuppressWarnings("rawtypes") + @Override + protected boolean testZoomFactor(AndroidViewData viewData, Map parameters, double zoomFactor) + { + // It does not make sense to set as checked any of the UI Elements that use the zoom in/out command + // Those elements do not represent states, but actions. + return false; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/AndroidEmulatorPerspective.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/AndroidEmulatorPerspective.java new file mode 100644 index 0000000..e1ba398 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/AndroidEmulatorPerspective.java @@ -0,0 +1,254 @@ +/* +* 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.ui.perspective; + +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.util.Collection; +import java.util.TreeSet; + +import org.eclipse.ui.IFolderLayout; +import org.eclipse.ui.IPageLayout; +import org.eclipse.ui.IPerspectiveFactory; +import org.eclipse.ui.PartInitException; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.ui.perspective.extension.AndroidPerspectiveExtensionBean; +import com.motorola.studio.android.emulator.ui.perspective.extension.AndroidPerspectiveExtensionBean.PerspectiveAreas; +import com.motorola.studio.android.emulator.ui.perspective.extension.AndroidPerspectiveExtensionReader; +import com.motorola.studio.android.emulator.ui.perspective.extension.IAndroidPerspectiveExtensionConstants; +import com.motorola.studio.android.emulator.ui.view.AndroidView; +import com.motorola.studio.android.emulator.ui.view.MainDisplayView; + +/** + * DESCRIPTION: + * This class represents the Android Emulator perspective. + * + * RESPONSIBILITY: + * Create the Android Emulator perspective. + * + * COLABORATORS: + * None + * + * USAGE: + * This class is referenced by the plugin.xml file of this plugin. + */ +public class AndroidEmulatorPerspective implements IPerspectiveFactory +{ + private static String LAUNCH_COOLBAR_SHORTCUT = "org.eclipse.debug.ui.launchActionSet"; + + /** + * Creates the initial layout for a page. + * + * @param layout the page layout + * + * @see IPerspectiveFactory#createInitialLayout(IPageLayout) + */ + public void createInitialLayout(IPageLayout layout) + { + // ---------------HOW THE PERSPECTIVE IS LAID OUT--------------- + // + // The Android Perspective will be dynamically populated according to + // the contributions declared to it through the androidPerspectiveExtension + // extension point. + // The perspective consists of three areas: + // - the area where the Android Emulator View is placed, on the right side + // - the area where device management related views are placed, on the left side + // - the area for emulation views, on the middle + // The areas that are dynamically populated are the two last ones, and they will + // be referred to as 'dynamic areas' by methods. + // + // The first area is filled by the code on this method by itself, and the two other + // ones are filled depending on what is found for the extension point. + // The following drawing illustrates the position of each area on the workbench: + // + // ____________________________________ + // | | | | + // | | | | + // | Dev | | Moto | + // | Mgt | | magx | + // | Views | Emu Views Area | Emu | + // | Area | | View | + // | | | Area | + // | | | | + // |_______|___________________|_______| + // + // Device Management views are placed atop of each other on their respective area. + // Emulation views are laid out depending on the number of views declared. If there + // is only one emulation view, it is placed occupying the entire emulation views area. + // If there are two emulation views, they divide the emulation views area in half and + // occupy the area from bottom to top, as seen on the following drawing: + // + // ____________________________________ + // | | | | + // | | | | + // | Dev | Emu Views part 2 | Moto | + // | Mgt | | magx | + // | Views |___________________| Emu | + // | Area | | View | + // | | Emu Views part 1 | Area | + // | | | | + // |_______|___________________|_______| + // + // If there are three emulation views or more, they divide the emulation views area in + // three parts, which are occupied from bottom to top, as seen on the following drawing: + // + // ____________________________________ + // | | | | + // | | Emu Views part 3 | | + // | Dev |___________________| Moto | + // | Mgt | | magx | + // | Views | Emu Views part 2 | Emu | + // | Area |___________________| View | + // | | | Area | + // | | Emu Views part 1 | | + // |_______|___________________|_______| + // + // If there are no device management or no emulation views (or both), their folders are + // always created so that the workbench is always divided into the correct areas for + // better user experience. + // + // ------------------------------------------------------------- + + addEmulatorView(layout); + addRunCoolbar(layout); + createAndPopulateDynamicAreas(layout); + + // hide the editor area (not necessary on this perspective) + layout.setEditorAreaVisible(false); + } + + private void addEmulatorView(IPageLayout layout) + { + // emulator view is a sticky view, no place holder is necessary (only open it) + try + { + EclipseUtils.showView(AndroidView.ANDROID_VIEW_ID); + } + catch (PartInitException e) + { + error("Unable to open Android Emulator View on Android Emulator Perspective."); + } + + layout.addShowViewShortcut(AndroidView.ANDROID_VIEW_ID); + layout.addShowViewShortcut(MainDisplayView.EMULATOR_MAIN_DISPLAY_VIEW_ID); + } + + private void addRunCoolbar(IPageLayout layout) + { + layout.addActionSet(LAUNCH_COOLBAR_SHORTCUT); + } + + private void createAndPopulateDynamicAreas(IPageLayout layout) + { + // read the extensions for this perspective, as declared by the androidPerspectiveExtension + // extension point + Collection<AndroidPerspectiveExtensionBean> perspectiveExtensionBeans = + AndroidPerspectiveExtensionReader.readAndroidPerspectiveExtensions(); + + // the folder for placing device management related views + IFolderLayout devMgtArea = createDeviceMgtViewsArea(layout); + + // collection of emuy area view ids for later placement + // it is alphabetically ordered (so that a group of defined + // views always open on the same location) + Collection<String> emuAreaViewIds = new TreeSet<String>(); + + for (AndroidPerspectiveExtensionBean extensionBean : perspectiveExtensionBeans) + { + if (PerspectiveAreas.DEVICE_MANAGEMENT_VIEWS_AREA.equals(extensionBean.getArea())) + { + // put dev mgt views atop of each other on the folder + devMgtArea.addView(extensionBean.getViewId()); + } + else if (PerspectiveAreas.EMULATION_VIEWS_AREA.equals(extensionBean.getArea())) + { + // collect emu views for later placement + emuAreaViewIds.add(extensionBean.getViewId()); + } + else + { + // in case of something not expected, log the problem + warn("View of id " + extensionBean.getViewId() + + " could not be added to Android Emulator perspective"); + } + } + + // the number of emu views, for defining the number of folders necessary + int numEmuViews = emuAreaViewIds.size(); + + // create the emu area folders, which will be at most size 3 + IFolderLayout[] emuAreaFolders = createEmuArea(layout, numEmuViews); + + // place the views on the correct folder by using the leftover of dividing the + // number of the current view by number of maximum folders (3) + int i = 0; + for (String viewId : emuAreaViewIds) + { + emuAreaFolders[i % 3].addView(viewId); + i++; // next view + } + } + + private IFolderLayout createDeviceMgtViewsArea(IPageLayout layout) + { + return layout.createFolder(IAndroidPerspectiveExtensionConstants.ATT_AREA_DEVMGT_VALUE, + IPageLayout.LEFT, 0.30f, IPageLayout.ID_EDITOR_AREA); + } + + private IFolderLayout[] createEmuArea(IPageLayout layout, int numEmuViews) + { + // at most 3 folders are necessary + IFolderLayout[] emuAreaFolders = new IFolderLayout[3]; + + // the number of divisions the emu views area will be divided into + int div = (numEmuViews >= 3 ? 3 : (numEmuViews == 2 ? 2 : 1)); + + // the ratio for the first folder to be placed (the bottom one, which may turn + // to be the only one if div is equal to 1) + float ratio = 1.00f / div; + + // the bottom folder (#1) is always necessary + // for 3 folders, ratio = 0.33f; for 2 folders, ratio = 0.5f + emuAreaFolders[0] = + layout.createFolder("emuAreaBottom", IPageLayout.BOTTOM, ratio, + IPageLayout.ID_EDITOR_AREA); + + // create folder #2 only if necessary + if (numEmuViews >= 2) + { + // adjust ratio depending on the number of folders in total: + // for 3 folders, ratio = 0.67f; for 2 folders, ratio = 0.5f + if (numEmuViews > 2) + { + ratio = 2 * ratio; + } + emuAreaFolders[1] = + layout.createFolder("emuAreaMiddle", IPageLayout.TOP, ratio, "emuAreaBottom"); + } + + // create folder #3 only if necessary + if (numEmuViews >= 3) + { + // ratio is always half of folder #2 space + emuAreaFolders[2] = + layout.createFolder("emuAreaTop", IPageLayout.TOP, 0.50f, "emuAreaMiddle"); + } + + return emuAreaFolders; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/AndroidPerspectiveExtensionBean.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/AndroidPerspectiveExtensionBean.java new file mode 100644 index 0000000..cb109c1 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/AndroidPerspectiveExtensionBean.java @@ -0,0 +1,98 @@ +/* +* 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.ui.perspective.extension; + +/** + * DESCRIPTION: + * <br> + * This class is a bean carrying information on a particular declaration of the + * androidPerspectiveExtension extension point. + * <br> + * RESPONSIBILITY: + * <br> + * Carry information about a declaration of androidPerspectiveExtension. + * <br> + * COLABORATORS: + * <br> + * None + * <br> + * USAGE: + * <br> + * This class should be instantiated only by the reader of the extension points. + * Clients may only get the values stored through the getter methods. + */ +public class AndroidPerspectiveExtensionBean +{ + private String viewId; + + private PerspectiveAreas area; + + /** + * Creates a new AndroidPerspectiveExtensionBean object with the given information. + * + * @param viewId the id of the view + * @param area the area to which it should be placed + */ + AndroidPerspectiveExtensionBean(String viewId, String area) + { + this.viewId = viewId; + + if (IAndroidPerspectiveExtensionConstants.ATT_AREA_DEVMGT_VALUE.equals(area)) + { + this.area = PerspectiveAreas.DEVICE_MANAGEMENT_VIEWS_AREA; + } + else if (IAndroidPerspectiveExtensionConstants.ATT_AREA_EMU_VALUE.equals(area)) + { + this.area = PerspectiveAreas.EMULATION_VIEWS_AREA; + } + else + { + this.area = PerspectiveAreas.UNKNOWN_AREA; + } + } + + /** + * Retrieves the id of the view. + * + * @return the id of the view + */ + public String getViewId() + { + return viewId; + } + + /** + * Retrieves the area to which the view should be added. + * + * @return the area on the perspective + */ + public PerspectiveAreas getArea() + { + return area; + } + + /** + * + * This enum represents the available areas to which a view can be added to the Android + * Emulator Perspective. + * It has a value for UNKNOWN_AREA for robustness purpose. + * + */ + public static enum PerspectiveAreas + { + DEVICE_MANAGEMENT_VIEWS_AREA, EMULATION_VIEWS_AREA, UNKNOWN_AREA + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/AndroidPerspectiveExtensionReader.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/AndroidPerspectiveExtensionReader.java new file mode 100644 index 0000000..873f2f9 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/AndroidPerspectiveExtensionReader.java @@ -0,0 +1,90 @@ +/* +* 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.ui.perspective.extension; + +import static com.motorola.studio.android.common.log.StudioLogger.warn; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; + +import com.motorola.studio.android.common.utilities.EclipseUtils; + +/** + * DESCRIPTION: + * <br> + * This class is the reader for the androidPerspectiveExtension extension point declarations. + * <br> + * RESPONSIBILITY: + * <br> + * Retrieve information from plugins declaring the androidPerspectiveExtension. + * <br> + * COLABORATORS: + * <br> + * IAndroidPerspectiveExtensionConstants: implements this interface for using the constants directly + * <br> + * USAGE: + * <br> + * This class methods should be called statically from within the implementation of the Android + * Emulator Perspective. + */ +public class AndroidPerspectiveExtensionReader implements IAndroidPerspectiveExtensionConstants +{ + + /** + * Reads the information from plug-ins declaring extensions to androidPerspectiveExtension + * and stores it into beans. + * + * @return a collection of beans containing androidPerspectiveExtension information + */ + public static Collection<AndroidPerspectiveExtensionBean> readAndroidPerspectiveExtensions() + { + Collection<AndroidPerspectiveExtensionBean> beans = + new LinkedHashSet<AndroidPerspectiveExtensionBean>(); + + IExtension[] extensions = EclipseUtils.getInstalledPlugins(EXTENSION_POINT_ID); + + for (IExtension extension : extensions) + { + IConfigurationElement[] viewElements = extension.getConfigurationElements(); + + for (IConfigurationElement viewElement : viewElements) + { + if (ELEMENT_VIEW.equals(viewElement.getName())) + { + String viewId = viewElement.getAttribute(ATT_ID); + String area = viewElement.getAttribute(ATT_AREA); + + // create the bean only if information could be read + if ((viewId != null) && (!viewId.equals("")) && (area != null) + && (!area.equals(""))) + { + beans.add(new AndroidPerspectiveExtensionBean(viewId, area)); + } + else + { + warn("View not added to Android Emulator Perspective area for extension " + + extension.getExtensionPointUniqueIdentifier()); + } + } + } + } + + return beans; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/IAndroidPerspectiveExtensionConstants.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/IAndroidPerspectiveExtensionConstants.java new file mode 100644 index 0000000..3d1424b --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/perspective/extension/IAndroidPerspectiveExtensionConstants.java @@ -0,0 +1,39 @@ +/* +* 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.ui.perspective.extension; + +import com.motorola.studio.android.emulator.EmulatorPlugin; + +/** + * + * This interface provides the String values used by the androidPerspectiveExtension + * extension point for purpose of reading the declared extensions. + * + */ +public interface IAndroidPerspectiveExtensionConstants +{ + String EXTENSION_POINT_ID = EmulatorPlugin.PLUGIN_ID + ".androidPerspectiveExtension"; + + String ELEMENT_VIEW = "view"; + + String ATT_ID = "id"; + + String ATT_AREA = "area"; + + String ATT_AREA_DEVMGT_VALUE = "devicemanagementviews"; + + String ATT_AREA_EMU_VALUE = "emulationviews"; +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AbstractAndroidView.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AbstractAndroidView.java new file mode 100644 index 0000000..cf6cb0f --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AbstractAndroidView.java @@ -0,0 +1,1839 @@ +/* +* 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.ui.view; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; +import static com.motorola.studio.android.common.log.StudioLogger.error; +import static com.motorola.studio.android.common.log.StudioLogger.info; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.osgi.util.NLS; +import org.eclipse.sequoyah.device.framework.model.IInstance; +import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate; +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolMessage; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.IRemoteDisplay; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IPerspectiveDescriptor; +import org.eclipse.ui.IPerspectiveListener; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IViewSite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchListener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPartReference; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.ViewPart; + +import com.motorola.studio.android.common.preferences.DialogWithToggleUtils; +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.common.utilities.PluginUtils; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager; +import com.motorola.studio.android.emulator.core.exception.InstanceStopException; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.exception.StartCancelledException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.model.IEmulatorView; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic; +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic.LogicMode; +import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; +import com.motorola.studio.android.emulator.logic.StartVncServerLogic; +import com.motorola.studio.android.emulator.logic.stop.AndroidEmulatorStopper; +import com.motorola.studio.android.emulator.ui.IUIHelpConstants; +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.emulator.ui.controls.RemoteCLIDisplay; +import com.motorola.studio.android.emulator.ui.controls.nativewindow.NativeWindowComposite; +import com.motorola.studio.android.nativeos.IDevicePropertiesOSConstants; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * DESCRIPTION: + * This class represents the Android Emulator view. It provides the + * generic methods of the Emulator Views. The specific ones must be defined + * by the classes that extend it. + * + * RESPONSIBILITY: + * - Show the viewers to the end user + * + * COLABORATORS: + * None. + * + * USAGE: + * The public interface provides static and dynamic methods: + * STATIC METHODS: + * - Call getActiveInstance to retrieve the instance that corresponds to + * the emulator running at the active tab + * - Call updateActiveViewers to guarantee that the active viewer of all views + * is up to date in all emulator views opened, but do not make further verifications. + * DYNAMIC METHODS: + * - Call refreshView to updates all viewers including creation of viewers + * for started instances and removal of viewers + * - Call updateActiveViewer to guarantee that the active viewer is up to + * date in all emulator views opened, but do not make further verifications + */ +public abstract class AbstractAndroidView extends ViewPart implements IEmulatorView +{ + private final MenuManager menuManager; + + public static final String POPUP_MENU_ID = "com.motorola.studio.android.emulator.view.popup"; + + private MouseListener mouseClickListener; + + /** + * Preference key of the Question Dialog about stopping the emulators by closing view + */ + private static String STOP_BY_CLOSING_VIEW_KEY_PREFERENCE = "stop.by.closing.view"; + + /** + * Preference key of the Question Dialog about displaying all emulators in the IDE + * + */ + private static String SHOW_EMULATOR_IN_THE_IDE_KEY_PREFERENCE = + "show.view.for.started.emulators"; + + /** + * Preference key of the Question Dialog about stopping all emulators in shutdown + * + */ + private static String STOP_ALL_EMULATORS_IN_SHUTDOWN_KEY_PREFERENCE = + "stop.all.emulators.in.shutdown"; + + /** + * All event types handled by the listeners in this class + */ + public static final int[] SWT_EVENT_TYPES = new int[] + { + SWT.KeyDown, SWT.KeyUp, SWT.MouseDown, SWT.MouseUp, SWT.MouseMove, SWT.MouseDoubleClick + }; + + /** + * All possible Layout Operations + */ + public enum LayoutOpp + { + KEEP, NEXT + }; + + /** + * Tab folder where to place each instance tab + */ + private TabFolder tabFolder; + + Listener listener = new Listener() + { + public void handleEvent(Event event) + { + if (tabFolder.getItemCount() > 0) + { + TabItem activeTabItem = getActiveTabItem(); + + if ((activeTabItem != null) && (activeTabItem.getControl() != null)) + { + info("Setting focus to Android Emulator " + activeTabItem.getData()); + activeTabItem.getControl().setFocus(); + } + } + } + }; + + /** + * Map to collect the emulator AndroidViewData committed to its emulator instance. + */ + private final Map<IAndroidEmulatorInstance, AndroidViewData> instanceDataMap = + new LinkedHashMap<IAndroidEmulatorInstance, AndroidViewData>(); + + /** + * Listener required to code the work-around for Sticky Views on perspectiveChanged method. + */ + private PerspectiveListenerImpl perspectiveListenerImpl; + + /** + * Listener necessary to determine when the view is closed. + */ + private PartListenerImpl partListenerImpl; + + /** + * Listener used to know if the view is being closed due to workbench shutdown. + */ + private static WorkbenchListenerImpl workbenchListenerImpl; + + // the view is being closed during the Studio shutdown. + private boolean closingOnShutdown = false; + + // Collection of the ids of the opened views that overwrite this class + private static final List<String> childrenIDs = new ArrayList<String>(); + + // the instance being currently active at the emulator views + private static IAndroidEmulatorInstance activeInstance; + + /** + * Listeners of tab switch event + */ + private static final Collection<Listener> tabSwitchListeners = new ArrayList<Listener>(); + + /** + * Lock to assure that only the first thread will display the show view question + */ + private static Lock showViewLock = new ReentrantReadWriteLock().writeLock(); + + /** + * Add a listener to be called when the tab selection changes + * + * @param listener the listener to be added + */ + public static void addTabSwitchListener(Listener listener) + { + tabSwitchListeners.add(listener); + } + + /** + * Remove a listener that listen to tab switch events + * + * @param listener the listener to be removed + */ + public static void removeTabSwitchListener(Listener listener) + { + tabSwitchListeners.remove(listener); + } + + /** + * Call listeners of tab switch events + */ + protected void handleTabSwitchEvent() + { + for (Listener listener : tabSwitchListeners) + { + listener.handleEvent(null); + } + } + + /** + * Returns the View Identification. + * @return the unique ViewId + */ + protected abstract String getViewId(); + + /** + * Creates the graphical elements representing the emulator that will be + * shown by the viewer in its tab item. + * + * @param tab the tab item that will hold the graphical elements that + * represents the emulator + * @param instance the emulator instance + * @param emulatorData the object to be defined with the elements created. + * @throws SkinException if the AVD configured skin does not exists + */ + protected abstract void createWidgets(TabItem tab, final IAndroidEmulatorInstance instance, + final AndroidViewData emulatorData) throws SkinException; + + /** + * Forces the refreshing of the menu elements. + */ + protected abstract void refreshMenuElements(); + + /** + * Retrieves the instance being currently active at the emulator views. + * + * @return The active instance, or null if there is no active instance + */ + public static IAndroidEmulatorInstance getActiveInstance() + { + return activeInstance; + } + + /** + * Retrieves the instance being currently active at the emulator views. + * + * @return The active instance, or null if there is no active instance + */ + public static void setInstance(IAndroidEmulatorInstance emulatorInstance) + { + if (!childrenIDs.isEmpty()) + { + AbstractAndroidView view = + (AbstractAndroidView) EclipseUtils.getActiveView(childrenIDs.get(0)); + if (view != null) + { + view.setActiveInstanceId(emulatorInstance.getInstanceIdentifier()); + activeInstance = emulatorInstance; + } + } + } + + /** + * Retrieves the information about the viewer used to display the given emulator instance + * and returns null if there is no AndroidViewData for the given emulator instance. + * viewer. + * @param the emulator instance whose Android Viewer data need to be retrieved. + * @return the AndroidViewerData for the given Emulator instance (null if none is available). + */ + public AndroidViewData getViewData(IAndroidEmulatorInstance instance) + { + AndroidViewData viewData = instanceDataMap.get(instance); + return viewData; + } + + public IAndroidSkin getSkin(IAndroidEmulatorInstance instance) + { + IAndroidSkin skin = null; + AndroidViewData viewData = getViewData(instance); + if (viewData != null) + { + skin = viewData.getSkin(); + } + return skin; + } + + /** + * Gets the layout to set, if opp is NEXT or PREVIOUS + * + * @param viewId The view that is currently active + * @param opp The layout operation to perform + */ + public static String getPreviousOrNextLayout(String viewId, LayoutOpp opp) + { + String prevNextLayout = null; + AbstractAndroidView view = (AbstractAndroidView) EclipseUtils.getActiveView(viewId); + if (view != null) + { + prevNextLayout = view.getPreviousOrNextLayout(opp); + } + + return prevNextLayout; + } + + /** + * Gets the layout to set, if opp is NEXT or PREVIOUS + * + * @param opp The layout operation to perform + */ + @SuppressWarnings("incomplete-switch") + private String getPreviousOrNextLayout(LayoutOpp opp) + { + String prevNextLayout = null; + if (activeInstance != null) + { + String referenceLayout = activeInstance.getCurrentLayout(); + AndroidViewData viewData = instanceDataMap.get(activeInstance); + if (viewData != null) + { + IAndroidComposite androidComposite = viewData.getComposite(); + if ((androidComposite != null)) + { + IAndroidSkin androidSkin = viewData.getSkin(); + + if (androidSkin != null) + { + switch (opp) + { + case NEXT: + prevNextLayout = androidSkin.getNextLayout(referenceLayout); + break; + } + } + } + } + } + + return prevNextLayout; + } + + /** + * Updates the zoom action that needs to be checked in all emulator views. + * This method must be called every time the focus changes to another + * viewer. + * + * @param layoutName The layout name to set + */ + public static void changeLayout(String layoutName) + { + for (String viewId : childrenIDs) + { + AbstractAndroidView view = (AbstractAndroidView) EclipseUtils.getActiveView(viewId); + if (view != null) + { + view.updateActiveViewer(layoutName); + } + } + } + + /** + * Gets the help ID to be used for attaching + * context sensitive help. + * + * Classes that extends this class and want to set + * their on help should override this method + */ + protected String getHelpId() + { + return IUIHelpConstants.EMULATOR_VIEW_HELP; + } + + /** + * @see org.eclipse.ui.IWorkbenchPart#createPartControl(Composite) + */ + @Override + public void createPartControl(Composite parent) + { + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, getHelpId()); + + this.tabFolder = new TabFolder(parent, SWT.BORDER | SWT.BACKGROUND); + IViewSite viewSite = getViewSite(); + + // Add listeners + tabFolder.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent event) + { + setActiveInstanceId(); + updateMenuAndToolbars(); + handleTabSwitchEvent(); + } + }); + + tabFolder.addFocusListener(new FocusAdapter() + { + + @Override + public void focusGained(FocusEvent e) + { + handleTabSwitchEvent(); + } + + }); + + perspectiveListenerImpl = new PerspectiveListenerImpl(); + viewSite.getWorkbenchWindow().addPerspectiveListener(perspectiveListenerImpl); + + partListenerImpl = new PartListenerImpl(); + viewSite.getPage().addPartListener(partListenerImpl); + + if (workbenchListenerImpl == null) + { + workbenchListenerImpl = new WorkbenchListenerImpl(); + viewSite.getWorkbenchWindow().getWorkbench() + .addWorkbenchListener(workbenchListenerImpl); + } + + // Update UI + refreshView(); + + IActionBars actionBars = viewSite.getActionBars(); + if (actionBars != null) + { + IMenuManager menuManager = actionBars.getMenuManager(); + if (menuManager != null) + { + menuManager.addMenuListener(new IMenuListener() + { + public void menuAboutToShow(IMenuManager manager) + { + // Calls the manager update method to guarantee that the command have its handler + // initialized. Otherwise, the next command will not work properly + if (manager != null) + { + manager.update(true); + } + updateMenuAndToolbars(); + } + }); + } + } + + //register the popup menu + viewSite.registerContextMenu(POPUP_MENU_ID, menuManager, null); + + //create listener + if (Platform.getOS().contains(Platform.OS_MACOSX)) + { + mouseClickListener = new MouseListener() + { + + public void mouseDoubleClick(MouseEvent e) + { + //do nothing + } + + public void mouseDown(MouseEvent e) + { + if ((e.button == 1) && (e.stateMask == SWT.CONTROL)) + { + menuManager.getMenu().setVisible(true); + } + } + + public void mouseUp(MouseEvent e) + { + //do nothing + } + + }; + } + else + { + mouseClickListener = new MouseListener() + { + + public void mouseDoubleClick(MouseEvent e) + { + //do nothing + } + + public void mouseDown(MouseEvent e) + { + if (e.button == 3) + { + menuManager.getMenu().setVisible(true); + } + } + + public void mouseUp(MouseEvent e) + { + //do nothing + } + + }; + + } + + } + + /** + * Constructor default + */ + public AbstractAndroidView() + { + childrenIDs.add(getViewId()); + menuManager = new MenuManager("", POPUP_MENU_ID); + addTabSwitchListener(listener); + } + + /** + * @see org.eclipse.ui.IWorkbenchPart#setFocus() + */ + @Override + public void setFocus() + { + if (tabFolder.getItemCount() > 0) + { + TabItem activeTabItem = getActiveTabItem(); + + if ((activeTabItem != null) && (activeTabItem.getControl() != null)) + { + info("Setting focus to Android Emulator " + activeTabItem.getData()); + activeTabItem.getControl().setFocus(); + } + else + { + info("Setting focus to Android Emulator View"); + tabFolder.setFocus(); + } + } + else + { + info("Setting focus to Android Emulator View"); + tabFolder.setFocus(); + } + + updateMenuAndToolbars(); + } + + /** + * @see org.eclipse.ui.IWorkbenchPart#dispose() + */ + @Override + public void dispose() + { + removeTabSwitchListener(listener); + debug("Disposing View: " + getClass()); + getViewSite().getWorkbenchWindow().removePerspectiveListener(perspectiveListenerImpl); + getViewSite().getPage().removePartListener(partListenerImpl); + perspectiveListenerImpl = null; + partListenerImpl = null; + instanceDataMap.clear(); + tabFolder.dispose(); + childrenIDs.remove(getViewId()); + super.dispose(); + } + + /** + * This method rebuilds the skin, adding a new tab in the Android Emulator View + * to show it. + * + * It should be used when the Android Emulator view is being created when the Android Emulator + * instance is not stopped. + */ + public void refreshView() + { + Job refreshViews = new Job("Refresh Emulator View") + { + @Override + protected IStatus run(IProgressMonitor monitor) + { + + info("Updating Android Emulator viewers"); + + final DeviceFrameworkManager framework = DeviceFrameworkManager.getInstance(); + + Collection<IAndroidEmulatorInstance> startedInstances = + framework.getAllStartedInstances(); + + for (final IAndroidEmulatorInstance instance : startedInstances) + { + if (instance + .getProperties() + .getProperty(IDevicePropertiesOSConstants.useVnc, + NativeUIUtils.getDefaultUseVnc()).equals("true")) + { + if (!instance.isConnected()) + { + IStatus returnStatus = null; + returnStatus = connectVNC(instance, monitor); + if (returnStatus.isOK()) + { + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + createViewer(instance); + } + }); + } + } + } + } + + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + + Collection<IAndroidEmulatorInstance> connectedInstances = + framework.getAllConnectedInstances(); + + Collection<IAndroidEmulatorInstance> instancesWithViewerCollection = + getInstancesWithViewer(); + + for (IAndroidEmulatorInstance instance : connectedInstances) + { + if (!instancesWithViewerCollection.contains(instance)) + { + createViewer(instance); + } + else + { + // update the collection for removing the stopped instances + instancesWithViewerCollection.remove(instance); + } + } + + // Remove not started instances from viewer + for (IAndroidEmulatorInstance instance : instancesWithViewerCollection) + { + disposeViewer(instance); + info("Disposed viewer of " + instance); + } + + // Update the active instance variable after any creation/disposal is + // made. Update the active viewer only if the active viewer is new + activeInstance = getActiveInstanceFromCurrentView(); + if (activeInstance != null) + { + setActiveInstanceId(); + handleTabSwitchEvent(); + } + + updateMenuAndToolbars(); + } + + }); + + return Status.OK_STATUS; + } + }; + refreshViews.setRule(new RefreshRule()); + refreshViews.schedule(); + } + + class RefreshRule implements ISchedulingRule + { + public boolean contains(ISchedulingRule rule) + { + return this == rule; + } + + public boolean isConflicting(ISchedulingRule rule) + { + return rule instanceof RefreshRule; + } + } + + /** + * Updates the zoom action that needs to be checked. + * This method must be called every time the focus changes to another + * viewer. + */ + public void updateActiveViewer() + { + updateActiveViewer(null); + } + + /** + * Updates the zoom action that needs to be checked, after performing a layout operation + * + * @param layoutName The name of the layout to set if opp is SETLAYOUT + */ + public void updateActiveViewer(String layoutName) + { + info("Updating Android Emulator view"); + + if (activeInstance != null) + { + AndroidViewData viewData = instanceDataMap.get(activeInstance); + if (viewData != null) + { + IAndroidComposite androidComposite = viewData.getComposite(); + if ((androidComposite != null)) + { + if ((activeInstance.getProperties().getProperty( + IDevicePropertiesOSConstants.useVnc, NativeUIUtils.getDefaultUseVnc())) + .equals("true")) + { + IAndroidSkin androidSkin = viewData.getSkin(); + + if (androidSkin != null) + { + if (layoutName != null) + { + activeInstance.setCurrentLayout(layoutName); + } + + boolean isNeeded = + androidSkin.isSwapWidthHeightNeededAtLayout(activeInstance + .getCurrentLayout()); + IRemoteDisplay.Rotation rotation = + (isNeeded + ? IRemoteDisplay.Rotation.ROTATION_90DEG_COUNTERCLOCKWISE + : IRemoteDisplay.Rotation.ROTATION_0DEG); + viewData.getMainDisplay().setRotation(rotation); + androidComposite.applyLayout(activeInstance.getCurrentLayout()); + } + } + androidComposite.applyZoomFactor(); + } + + } + } + + updateMenuAndToolbars(); + + info("Updated Android Emulator view"); + } + + public void changeToNextLayout() + { + AndroidViewData viewData = instanceDataMap.get(activeInstance); + IAndroidComposite androidComposite = viewData.getComposite(); + if (androidComposite instanceof NativeWindowComposite) + { + ((NativeWindowComposite) androidComposite).changeToNextLayout(); + } + } + + /** + * Retrieves the instance being currently displayed at this view. + * + * @return The active instance, or null if there is no active instance + */ + private IAndroidEmulatorInstance getActiveInstanceFromCurrentView() + { + TabItem activeInstanceItem = getActiveTabItem(); + IAndroidEmulatorInstance instance = null; + if (activeInstanceItem != null) + { + instance = (IAndroidEmulatorInstance) (activeInstanceItem.getData()); + } + else + { + debug("No active instance being shown at emulator view"); + } + + return instance; + } + + /** + * Executes the procedure to connect to the VNC + * + * @param androidDevice + * The device being connected + */ + public IStatus connectVNC(final IAndroidEmulatorInstance instance, IProgressMonitor monitor) + { + IStatus statusToReturn = Status.OK_STATUS; + + try + { + IAndroidLogicInstance logicInstance = (IAndroidLogicInstance) instance; + AbstractStartAndroidEmulatorLogic startLogic = logicInstance.getStartLogic(); + + startLogic.execute(logicInstance, LogicMode.TRANSFER_AND_CONNECT_VNC, + logicInstance.getTimeout(), monitor); + } + catch (StartCancelledException e1) + { + info("The user canceled the transfer/connect to VNC phase."); + statusToReturn = Status.CANCEL_STATUS; + } + catch (Exception e1) + { + error("Could not establish VNC Connection to " + instance); + statusToReturn = + new Status(IStatus.ERROR, EmulatorPlugin.PLUGIN_ID, NLS.bind( + EmulatorNLS.ERR_CannotConnectToVNC, instance.getName())); + } + return statusToReturn; + } + + /** + * Shows the Android Emulator view, if not being shown + */ + public static void showView() + { + info("Open and move focus to the emulator view"); + + boolean emulatorViewOpened = + !EclipseUtils.getAllOpenedViewsWithId(AndroidView.ANDROID_VIEW_ID).isEmpty(); + + try + { + // if emulator view is opened previously or if no emulator view is opened, + // show / refresh the emulator view. + if (emulatorViewOpened) + { + EclipseUtils.showView(AndroidView.ANDROID_VIEW_ID); + } + else + { + // Make sure only one open view (due to the transition to online) will occur at the same time. + // e.g. if the "question open dialog" is already opened, it is not needed one + + if (showViewLock.tryLock()) + { + try + { + + boolean openEmulatorView = + DialogWithToggleUtils + .showQuestion( + SHOW_EMULATOR_IN_THE_IDE_KEY_PREFERENCE, + EmulatorNLS.QUESTION_AbstractAndroidView_OpenViewForStartedEmulatorsTitle, + EmulatorNLS.QUESTION_AbstractAndroidView_OpenViewForStartedEmulatorsMessage); + if (openEmulatorView) + { + EclipseUtils.showView(AndroidView.ANDROID_VIEW_ID); + } + } + finally + { + showViewLock.unlock(); + } + } + } + } + catch (PartInitException e) + { + error("The Android Emulator View could not be opened programatically"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_AbstractAndroidView_ViewNotAccessibleProgramatically); + } + } + + /** + * Creates a viewer for the provided instance + * + * @param instance The instance that will have a viewer created at this view + */ + private void createViewer(final IAndroidEmulatorInstance instance) + { + if (instance != null) + { + info("Creating tab for " + instance + " on " + getClass()); + + Set<IAndroidEmulatorInstance> currentInstancesWithTab = + getInstancesWithAtLeastOneViewer(); + + // Creates a tab item to hold the skin at the view + TabItem newTabItem = new TabItem(tabFolder, SWT.NONE); + + // Set parameters at the tab item + newTabItem.setText(instance.getFullName()); + newTabItem.setData(instance); + AndroidViewData emulatorData = new AndroidViewData(); + instanceDataMap.put(instance, emulatorData); + + try + { + createWidgets(newTabItem, instance, emulatorData); + tabFolder.setSelection(newTabItem); + setActiveInstanceId(); + + //add popup menu + if (newTabItem.getControl() != null) + { + menuManager.createContextMenu(newTabItem.getControl()); + newTabItem.getControl().addMouseListener(mouseClickListener); + } + + ProtocolMessage setEncodingMsg = new ProtocolMessage(2); + setEncodingMsg.setFieldValue("padding", 0); + setEncodingMsg.setFieldValue("number-of-encodings", 1); + setEncodingMsg.setFieldValue("encoding-type", "encoding-types", 0, 0); + PluginProtocolActionDelegate.sendMessageToServer(instance.getProtocolHandle(), + setEncodingMsg); + + info("Created tab for " + instance); + + if (instance + .getProperties() + .getProperty(IDevicePropertiesOSConstants.useVnc, + NativeUIUtils.getDefaultUseVnc()).toString().equals("true")) + { + startVncDisplays(instance); + info("Started displays for " + instance); + + // overwrite original tml listeners + addListenersToMainDisplay(emulatorData); + } + else + { + IAndroidComposite parentComposite = emulatorData.getComposite(); + ((NativeWindowComposite) parentComposite).addMouseListener(mouseClickListener); + } + + IAndroidComposite androidComposite = emulatorData.getComposite(); + if (androidComposite != null) + { + androidComposite.applyZoomFactor(); + } + + // If this is the first view to be opened, guarantee that the screen orientation is + // synchronized with the current layout (only when using VNC) + if (!currentInstancesWithTab.contains(instance) + && instance + .getProperties() + .getProperty(IDevicePropertiesOSConstants.useVnc, + NativeUIUtils.getDefaultUseVnc()).toString().equals("true")) + { + IAndroidSkin skin = getSkin(instance); + if (skin != null) + { + instance.changeOrientation(skin.getLayoutScreenCommand(instance + .getCurrentLayout())); + } + } + + updateActiveViewer(); + + info("Created tab for Android Emulator " + instance); + } + catch (SkinException e) + { + error("The skin associated to this instance (" + instance.getName() + + ") is not installed or is corrupted."); + EclipseUtils.showErrorDialog(e); + + try + { + instance.stop(true); + disposeViewer(instance); + } + catch (InstanceStopException e1) + { + error("Error while running service for stopping virtual machine"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_General_CannotRunStopService); + } + } + } + } + + private void addListenersToMainDisplay(AndroidViewData emulatorData) + { + // TmL registers listeners during start, and unregisters all of them during + // stop. To adapt the listeners to Studio needs, we are including the following + // operations after the start display call from TmL. With this we are achieving: + // + // 1. TmL registers several listeners at its canvas (TmL start method) + // 2. Studio unregisters the TmL listeners (this method) + // 3. Studio registers new listeners to replace the TmL ones (this method) + // 4. TmL unregisters Studio listeners instead of its own (TmL stop method) + + SWTRemoteDisplay remoteDisplay = emulatorData.getMainDisplay(); + final Canvas canvas = remoteDisplay.getCanvas(); + IAndroidComposite parentComposite = emulatorData.getComposite(); + + for (int eventType : SWT_EVENT_TYPES) + { + for (Listener listener : canvas.getListeners(eventType)) + { + canvas.removeListener(eventType, listener); + } + } + + KeyListener keyListener = parentComposite.getKeyListener(); + final MouseListener mouseListener = parentComposite.getMouseListener(); + MouseMoveListener mouseMoveListener = parentComposite.getMouseMoveListener(); + + canvas.addKeyListener(keyListener); + canvas.addMouseListener(mouseListener); + canvas.addMouseMoveListener(mouseMoveListener); + + // Due to the differences in listener registration between TmL and Studio, it will + // remain a registered listener when the viewer is disposed. For this reason, the + // following dispose listener is being registered. + DisposeListener disposeListener = new DisposeListener() + { + public void widgetDisposed(DisposeEvent arg0) + { + canvas.removeMouseListener(mouseListener); + canvas.removeMouseListener(mouseClickListener); + } + }; + emulatorData.setDisposeListener(disposeListener); + canvas.addDisposeListener(disposeListener); + canvas.addMouseListener(mouseClickListener); + } + + /** + * Disposes the viewer of the provided instance + * + * @param instance The instance that will have a viewer disposed from this view + */ + private void disposeViewer(final IAndroidEmulatorInstance instance) + { + info("Disposing tab of Android Emulator at " + instance); + + TabItem item = getTabItem(instance); + if (item != null) + { + + stopVncDisplays(instance); + + //if there are no other viewers, we can stop protocol and vnc server + if ((childrenIDs.size() == 1) + && (instance + .getProperties() + .getProperty(IDevicePropertiesOSConstants.useVnc, + NativeUIUtils.getDefaultUseVnc()).toString().equals("true"))) + { + info("There is only one view opened, stop VNC protocol and VNC Server"); + stopVncProtocol((IAndroidLogicInstance) instance); + stopVncServer(instance); + } + + AndroidViewData data = instanceDataMap.get(instance); + if (data != null) + { + SWTRemoteDisplay mainDisplay = data.getMainDisplay(); + if (mainDisplay != null) + { + Canvas canvas = mainDisplay.getCanvas(); + + if (canvas != null) + { + canvas.removeDisposeListener(data.getDisposeListener()); + } + } + } + + Control c = item.getControl(); + if (c != null) + { + c.dispose(); + } + item.setControl(null); + + item.dispose(); + instanceDataMap.remove(instance); + updateMenuAndToolbars(); + info("Disposed tab of Android Emulator at " + instance); + } + + } + + /** + * Gets the list of instances with viewers associated. + * @return the collection of instances + */ + private Collection<IAndroidEmulatorInstance> getInstancesWithViewer() + { + final Collection<IAndroidEmulatorInstance> instancesWithViewer = + new LinkedHashSet<IAndroidEmulatorInstance>(); + + if (!tabFolder.isDisposed()) + { + final TabItem[] allItems = tabFolder.getItems(); + + for (TabItem item : allItems) + { + if (!item.isDisposed()) + { + instancesWithViewer.add((IAndroidEmulatorInstance) item.getData()); + } + } + } + + return instancesWithViewer; + } + + private static Set<IAndroidEmulatorInstance> getInstancesWithAtLeastOneViewer() + { + Set<IAndroidEmulatorInstance> instancesSet = new HashSet<IAndroidEmulatorInstance>(); + for (String viewId : childrenIDs) + { + AbstractAndroidView view = (AbstractAndroidView) EclipseUtils.getActiveView(viewId); + if (view != null) + { + instancesSet.addAll(view.getInstancesWithViewer()); + } + } + + return instancesSet; + } + + /** + * Gets the tab item related to the instance + * @param instance the emulator instance + * @return the tab item + */ + private TabItem getTabItem(IAndroidEmulatorInstance instance) + { + TabItem result = null; + if (!tabFolder.isDisposed()) + { + TabItem[] allItems = tabFolder.getItems(); + + for (TabItem item : allItems) + { + if (instance.equals(item.getData())) + { + result = item; + break; + } + } + } + + return result; + } + + /** + * Gets the tab item related to the instance + * @param instance the emulator instance + * @return the tab item + */ + private TabItem getTabItem(IInstance instance) + { + TabItem result = null; + if (!tabFolder.isDisposed()) + { + TabItem[] allItems = tabFolder.getItems(); + + for (TabItem item : allItems) + { + if (instance.getName() + .equals(((IAndroidEmulatorInstance) item.getData()).getName())) + { + result = item; + break; + } + } + } + + return result; + } + + /** + * Retrieves the active tab at view + * + * @return The active tab, or null of there is no tab at tab folder + */ + private TabItem getActiveTabItem() + { + int activeInstanceIndex = this.tabFolder.getSelectionIndex(); + + TabItem activeTabItem = null; + + if (activeInstanceIndex >= 0) + { + activeTabItem = this.tabFolder.getItem(activeInstanceIndex); + } + + return activeTabItem; + } + + /** + * Updates the zoom action that needs to be checked. + * This method must be called every time the focus changes to another + * viewer + */ + private void updateMenuAndToolbars() + { + IViewSite viewSite = getViewSite(); + + if (viewSite != null) + { + IActionBars actionBars = viewSite.getActionBars(); + + if (actionBars != null) + { + IMenuManager menuManager = actionBars.getMenuManager(); + updateMenuManager(menuManager, viewSite); + + IToolBarManager toolbarManager = actionBars.getToolBarManager(); + if (toolbarManager != null) + { + IContributionItem[] items = toolbarManager.getItems(); + for (IContributionItem item : items) + { + item.update(); + } + } + } + + refreshMenuElements(); + } + } + + /** + * Recursive method to update items at menus. The recursion helps to update submenus + * + * @param manager The manager that holds a menu items + * @param viewSite The current view site. + */ + private void updateMenuManager(IMenuManager manager, IViewSite viewSite) + { + // Update the items in menu manager + if (manager != null) + { + IContributionItem[] items = manager.getItems(); + for (IContributionItem item : items) + { + if (item instanceof IMenuManager) + { + updateMenuManager((IMenuManager) item, viewSite); + } + else + { + item.update(); + } + } + } + } + + /** + * Stops all emulator instances with the Progress Monitor opened. + */ + private void stopEmulatorInstances() + { + // defines the runnable object for stopping emulator instances. + final IRunnableWithProgress stopRunnable = new IRunnableWithProgress() + { + public void run(IProgressMonitor monitor) + { + Collection<IAndroidEmulatorInstance> startedInstances = + DeviceFrameworkManager.getInstance().getAllStartedInstances(); + boolean errorsHappened = false; + + for (IAndroidEmulatorInstance instance : startedInstances) + { + try + { + instance.stop(true); + } + catch (InstanceStopException e) + { + errorsHappened = true; + } + } + + // if closing on shutdown, use a progress bar and stall UI + if (closingOnShutdown) + { + // start a progress monitor + monitor.beginTask("", IProgressMonitor.UNKNOWN); + + // make sure the stop instance job finished + Job[] jobs = Job.getJobManager().find(null); // get all jobs + for (Job job : jobs) + { + if (job.getName() + .equals(EmulatorNLS.UI_AbstractAndroidView_StopInstanceJob)) + { + // when job result is not null, it has finished + while (job.getResult() == null) + { + try + { + // sleep a little so the waiting is not too busy + Thread.sleep(1000); + } + catch (InterruptedException e) + { + // do nothing + } + } + } + } + } + + if (errorsHappened) + { + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_AncroidView_CannotRunMultipleStopServices); + } + + } + }; + + // executes the runnable defined above. + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + Shell currentShell = getViewSite().getShell(); + ProgressMonitorDialog dialog = new ProgressMonitorDialog(currentShell); + try + { + dialog.run(true, false, stopRunnable); + } + catch (Exception e) + { + // Should not have exceptions. + // The runnable is not interrupted and it handles exceptions internally + // Log runtime exceptions + error("Runtime exception was thrown: " + e.getClass().getSimpleName()); + } + } + }); + } + + /** + * Sets the identifier of the instance being currently displayed at view + */ + private void setActiveInstanceId(String activeHost) + { + for (String viewId : childrenIDs) + { + Collection<IViewPart> viewsToUpdateMenu = EclipseUtils.getAllOpenedViewsWithId(viewId); + for (IViewPart view : viewsToUpdateMenu) + { + AbstractAndroidView emulatorView = (AbstractAndroidView) view; + emulatorView.setSelection(activeHost); + } + + } + } + + /** + * Sets the identifier of the instance being currently displayed at view + */ + private void setActiveInstanceId() + { + TabItem activeInstanceItem = getActiveTabItem(); + if ((activeInstanceItem != null) && (activeInstanceItem.getData() != null)) + { + + activeInstance = (IAndroidEmulatorInstance) activeInstanceItem.getData(); + + String activeId = + ((IAndroidEmulatorInstance) activeInstanceItem.getData()) + .getInstanceIdentifier(); + + setActiveInstanceId(activeId); + } + else + { + debug("No active instance being shown at emulator view"); + } + + } + + /** + * Starts the main display associating it to the protocol. + * @param handle the protocol handle + * @param mainDisplay the main display object + */ + private void startDisplay(ProtocolHandle handle, SWTRemoteDisplay mainDisplay) + { + // Stop any running screens + if ((mainDisplay.isActive()) && (!mainDisplay.isDisposed())) + { + mainDisplay.stop(); + } + + try + { + info("Starting main display refresh"); + mainDisplay.start(handle); + } + catch (Exception e) + { + error("Viewers could not be started."); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_AndroidView_ErrorStartingScreens); + + GC gc = new GC(mainDisplay.getCanvas()); + gc.fillRectangle(0, 0, mainDisplay.getScreenWidth(), mainDisplay.getScreenHeight()); + gc.dispose(); + } + } + + /** + * Starts viewer (main display and CLI display) of the emulator instance. + * @param instance the emulator instance + */ + protected void startVncDisplays(final IAndroidEmulatorInstance instance) + { + AndroidViewData viewData = instanceDataMap.get(instance); + if (viewData != null) + { + if (viewData.getMainDisplay() != null) + { + startDisplay(instance.getProtocolHandle(), viewData.getMainDisplay()); + } + if ((viewData.getCliDisplay() != null) && instance.getHasCli()) + { + viewData.getCliDisplay().start(); + } + } + } + + /** + * Stops viewer (main display and CLI display) of the emulator instance. + */ + private void stopVncDisplays(final IAndroidEmulatorInstance instance) + { + info("Stop the VNC Display " + getViewId() + " for " + instance); + AndroidViewData viewData = instanceDataMap.get(instance); + + if ((viewData != null)) + { + SWTRemoteDisplay mainDisplay = viewData.getMainDisplay(); + if ((mainDisplay != null) && mainDisplay.isActive() && !mainDisplay.isDisposed()) + { + mainDisplay.stop(); + if ((mainDisplay.getBackground() != null) + && !mainDisplay.getBackground().isDisposed()) + { + mainDisplay.getBackground().dispose(); + } + } + + RemoteCLIDisplay cliDisplay = viewData.getCliDisplay(); + if ((cliDisplay != null) && cliDisplay.isDisplayActive() && !cliDisplay.isDisposed()) + { + cliDisplay.stop(); + if ((cliDisplay.getBackground() != null) + && !cliDisplay.getBackground().isDisposed()) + { + cliDisplay.getBackground().dispose(); + } + } + } + } + + /** + * @param instance + */ + private void stopVncProtocol(IAndroidLogicInstance instance) + { + AndroidEmulatorStopper.stopInstance(instance, true, false, new NullProgressMonitor()); + + } + + /** + * Stops the execution of the vnc server if it is running on the given instance. + * This acts as if the Control+C was pressed in the shell where the vnc server is executing... + * @param instance + */ + private void stopVncServer(IAndroidEmulatorInstance instance) + { + StartVncServerLogic.cancelCurrentVncServerJobs(instance); + } + + /** + * Class to implement the IPerspectiveListener that you be used as ParListener2 of + * current page when the Emulator View is opened. It is required to code the + * work-around for Sticky Views on perspectiveChanged method. + */ + private class PerspectiveListenerImpl implements IPerspectiveListener + { + + public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) + { + // Nothing to do. + } + + public void perspectiveChanged(IWorkbenchPage page, IPerspectiveDescriptor perspective, + String changeId) + { + if (changeId.equals(IWorkbenchPage.CHANGE_VIEW_HIDE)) + { + + // if the emulator view was hidden + if (page.findView(getViewId()) == null) + { + + // This is a "sticky view" so when it is hidden or shown for one + // perspective, its state is remembered for the other ones. + // However, the view reference count is just updated when + // the current active perspective is changed. The code below + // forces the perspective changing in order to dispose the view + // immediately after it is hidden. + for (IPerspectiveDescriptor pd : page.getOpenPerspectives()) + { + if (!pd.equals(perspective)) + { + page.setPerspective(pd); + } + } + page.setPerspective(perspective); + } + } + } + } + + /** + * Class to implement IPartListener2 that you be used as ParListener2 of current page + * when the Emulator View is opened. It is necessary to determine when the view is + * closed. + */ + private class PartListenerImpl implements IPartListener2 + { + + public void partActivated(IWorkbenchPartReference partRef) + { + // Nothing to do. + } + + public void partBroughtToTop(IWorkbenchPartReference partRef) + { + // Nothing to do. + } + + public void partClosed(final IWorkbenchPartReference partRef) + { + + // if view that is being closed is not THIS view. + if (!partRef.getId().equals(getViewId())) + { + return; + } + + // executed on async mode to avoid UI blocking + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + boolean openedViewsExist = false; + + for (String viewId : childrenIDs) + { + if (!getViewId().equals(viewId) + && (partRef.getPage().findView(viewId) != null)) + { + openedViewsExist = true; + break; + } + } + + // stops all viewers and clear the tabs list + Collection<IAndroidEmulatorInstance> instances = getInstancesWithViewer(); + for (IAndroidEmulatorInstance instance : instances) + { + disposeViewer(instance); + } + + // if the tool is not being closed and there is no other emulator + // view opened. + if (!closingOnShutdown && !openedViewsExist) + { + + Collection<IAndroidEmulatorInstance> startedInstances = + DeviceFrameworkManager.getInstance().getAllStartedInstances(); + + boolean oneInstanceStarted = (startedInstances.size() > 0); + + if (oneInstanceStarted + && (DialogWithToggleUtils + .showQuestion( + STOP_BY_CLOSING_VIEW_KEY_PREFERENCE, + EmulatorNLS.QUESTION_AndroidView_StopAllInstancesOnDisposeTitle, + EmulatorNLS.QUESTION_AndroidView_StopAllInstancesOnDisposeMessage))) + { + + stopEmulatorInstances(); + } + } + + } + + }); + } + + public void partDeactivated(IWorkbenchPartReference partRef) + { + // Nothing to do. + } + + public void partHidden(IWorkbenchPartReference partRef) + { + // Nothing to do. + } + + public void partInputChanged(IWorkbenchPartReference partRef) + { + // Nothing to do. + } + + public void partOpened(IWorkbenchPartReference partRef) + { + // Nothing to do. + } + + public void partVisible(IWorkbenchPartReference partRef) + { + if (partRef.getId().equals(getViewId())) + { + refreshView(); + } + } + } + + /** + * Class to implement the IWorkbenchListener that you be used as WorkbenchListener of + * workbench when the Emulator View is opened. It is used to know if the view + * is being closed due to workbench shutdown. + */ + private class WorkbenchListenerImpl implements IWorkbenchListener + { + + public void postShutdown(IWorkbench workbench) + { + // Nothing to do. + } + + public boolean preShutdown(IWorkbench workbench, boolean forced) + { + closingOnShutdown = true; + + Collection<IAndroidEmulatorInstance> startedInstances = + DeviceFrameworkManager.getInstance().getAllStartedInstances(); + + if (startedInstances.size() > 0) + { + + boolean stopEmulatorInstances = false; + if (PluginUtils.getOS() != PluginUtils.OS_LINUX) + { + stopEmulatorInstances = + DialogWithToggleUtils.showQuestion( + STOP_ALL_EMULATORS_IN_SHUTDOWN_KEY_PREFERENCE, + EmulatorNLS.QUESTION_RunningInstancesOnClose_Title, + EmulatorNLS.QUESTION_RunningInstancesOnClose_Text); + } + else + { + DialogWithToggleUtils.showWarning( + STOP_ALL_EMULATORS_IN_SHUTDOWN_KEY_PREFERENCE, + EmulatorNLS.WARN_RunningInstancesOnClose_Linux_Title, + EmulatorNLS.WARN_RunningInstancesOnClose_Linux_Text); + //stopEmulatorInstances = true; + } + + if (stopEmulatorInstances) + { + stopEmulatorInstances(); + } + + } + + return true; + } + } + + /** + * Selects the tab that has this data host and set the activeHost + * @param host IP address + */ + private void setSelection(final String host) + { + + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + TabItem selectedTab = null; + + TabItem[] tabArray = tabFolder.getItems(); + + for (TabItem tabItem : tabArray) + { + String tabItemHost = + ((IAndroidEmulatorInstance) tabItem.getData()).getInstanceIdentifier(); + if ((host != null) && (host.equals(tabItemHost))) + { + selectedTab = tabItem; + break; + } + } + + if (selectedTab != null) + { + tabFolder.setSelection(selectedTab); + updateMenuAndToolbars(); + } + + } + }); + + } + + public static void updateInstanceName(final IInstance instance) + { + PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() + { + public void run() + { + if (!childrenIDs.isEmpty()) + { + AbstractAndroidView view = + (AbstractAndroidView) EclipseUtils.getActiveView(childrenIDs.get(0)); + if (view != null) + { + if ((instance != null)) + { + TabItem tabItem = view.getTabItem(instance); + if (tabItem != null) + { + tabItem.setText(((IAndroidEmulatorInstance) tabItem.getData()) + .getFullName()); + } + } + } + } + } + }); + } + + /** + * Sets the skin zoom factor + * + * @param instance the emulator instance + * @param zoom the zoom factor + */ + public final void setZoomFactor(IAndroidEmulatorInstance instance, double zoom) + { + try + { + AndroidViewData viewData = instanceDataMap.get(instance); + if (viewData != null) + { + IAndroidComposite composite = viewData.getComposite(); + if (composite != null) + { + composite.setZoomFactor(zoom); + } + } + } + catch (Exception e) + { + error("Detached zoom could not be set."); + } + } + + /** + * Gets the skin zoom factor + * + * @param instance the emulator instance + * @return the zoom factor + */ + public final double getZoomFactor(IAndroidEmulatorInstance instance) + { + double zoomFactor = 0.0; + AndroidViewData viewData = instanceDataMap.get(instance); + if (viewData != null) + { + IAndroidComposite composite = viewData.getComposite(); + if (composite != null) + { + zoomFactor = composite.getZoomFactor(); + } + } + return zoomFactor; + } +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AndroidView.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AndroidView.java new file mode 100644 index 0000000..2a05611 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AndroidView.java @@ -0,0 +1,265 @@ +/* +* 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.ui.view; + +import static com.motorola.studio.android.common.log.StudioLogger.error; + +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.sequoyah.vnc.vncviewer.config.IPropertiesFileHandler; +import org.eclipse.sequoyah.vnc.vncviewer.config.VNCConfiguration; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.ISWTPainter; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.img.SWTRemoteDisplayImg; +import org.eclipse.sequoyah.vnc.vncviewer.network.VNCProtocolData; +import org.eclipse.sequoyah.vnc.vncviewer.registry.VNCProtocolRegistry; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.ui.IViewSite; +import org.eclipse.ui.commands.ICommandService; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.exception.InstanceStopException; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.ISkinKeyXmlTags; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.emulator.ui.controls.maindisplay.MainDisplayComposite; +import com.motorola.studio.android.emulator.ui.controls.nativewindow.NativeWindowComposite; +import com.motorola.studio.android.emulator.ui.controls.skin.SkinComposite; +import com.motorola.studio.android.emulator.ui.handlers.IHandlerConstants; +import com.motorola.studio.android.nativeos.IDevicePropertiesOSConstants; +import com.motorola.studio.android.nativeos.NativeUIUtils; + +/** + * DESCRIPTION: This class represents the Android Emulator view + * + * RESPONSIBILITY: - Show the skin to the end user + * + * COLABORATORS: None. + * + * USAGE: All the public interface is extended from + * <code>AbstractAndroidView</code>. + */ +public class AndroidView extends AbstractAndroidView +{ + /** + * The Android Emulator view ID + */ + public static final String ANDROID_VIEW_ID = EmulatorPlugin.PLUGIN_ID + ".androidView"; + + /** + * @see com.motorola.studio.android.emulator.ui.view.AbstractAndroidView#getViewId() + */ + @Override + protected String getViewId() + { + return ANDROID_VIEW_ID; + } + + /** + * Creates the skin composite, main display and CLI display related to + * emulator. + * + * @param tab + * the tab item that will hold the graphical elements that + * represents the emulator + * @param instance + * the emulator instance + * @param emulatorData + * the object to be defined with the elements created. + */ + @Override + protected void createWidgets(TabItem tab, final IAndroidEmulatorInstance instance, + final AndroidViewData tabData) throws SkinException + { + tabData.loadSkin(instance); + IAndroidSkin skin = tabData.getSkin(); + + ProtocolHandle handle = instance.getProtocolHandle(); + VNCProtocolData data = VNCProtocolRegistry.getInstance().get(handle); + + final SWTRemoteDisplay mainDisplay; + Composite parentComposite; + if (instance.getProperties().getProperty(IDevicePropertiesOSConstants.useVnc, + NativeUIUtils.getDefaultUseVnc()).toString().equals("true")) + { + if (data != null) + { + if (instance.getProperties().getProperty("Command_Line").contains("-no-skin")) + { + int baseWidth = + skin.getSkinBean(instance.getCurrentLayout()).getSkinPropertyValue( + ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH); + int baseHeight = + skin.getSkinBean(instance.getCurrentLayout()).getSkinPropertyValue( + ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT); + + ScrolledComposite scrolledComposite = + new ScrolledComposite(tab.getParent(), SWT.H_SCROLL | SWT.V_SCROLL + | SWT.BACKGROUND); + + final MainDisplayComposite composite = + new MainDisplayComposite(scrolledComposite, SWT.BACKGROUND, baseWidth, + baseHeight, instance); + composite.setLayout(new FillLayout()); + + mainDisplay = + createMainDisplay(composite, skin, instance, (ISWTPainter) data + .getVncPainter()); + composite.setSize(baseWidth, baseHeight); + + scrolledComposite.setContent(composite); + tab.setControl(scrolledComposite); + + scrolledComposite.addDisposeListener(new DisposeListener() + { + public void widgetDisposed(DisposeEvent e) + { + composite.dispose(); + } + }); + + tabData.setCliDisplay(null); + tabData.setComposite(composite); + tabData.setMainDisplay(mainDisplay); + } + else + { + parentComposite = createSkinComposite(tab.getParent(), skin, instance); + mainDisplay = + createMainDisplay(parentComposite, skin, instance, (ISWTPainter) data + .getVncPainter()); + + tabData.setComposite((IAndroidComposite) parentComposite); + tabData.setMainDisplay(mainDisplay); + tab.setControl(parentComposite); + } + } + else + { + error("The protocol object set in the device instance is not supported. Stopping the emulator instance..."); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.ERR_AndroidView_ProtocolImplementerNotSupported); + + try + { + instance.stop(true); + } + catch (InstanceStopException e) + { + error("Error while running service for stopping virtual machine"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_General_CannotRunStopService); + } + } + + } + else + { + parentComposite = createNativeWindowComposite(tab.getParent(), skin, instance); + tabData.setComposite((IAndroidComposite) parentComposite); + tab.setControl(parentComposite); + } + } + + /** + * Creates the skin composite used by this instance + * + * @param parent + * The parent composite where to place the widgets + * @param skin + * The object containing skin information used to draw the + * widgets + */ + private SkinComposite createSkinComposite(Composite parent, IAndroidSkin skin, + IAndroidEmulatorInstance instance) + { + + SkinComposite skinComposite = new SkinComposite(parent, skin, instance); + skinComposite.setBackground(new Color(skinComposite.getDisplay(), 255, 255, 255)); + + return skinComposite; + } + + private NativeWindowComposite createNativeWindowComposite(Composite parent, IAndroidSkin skin, + IAndroidEmulatorInstance instance) + { + NativeWindowComposite nativeWindowComposite = + new NativeWindowComposite(parent, skin, instance); + // nativeWindowComposite.setBackground(new Color(nativeWindowComposite.getDisplay(), 255, 255, + // 255)); + + return nativeWindowComposite; + } + + /** + * Creates the main display object used by this instance. + * + * @param parent + * The parent composite where to place the widgets + * @param skin + * The object containing skin information used to draw the + * widgets + * @param instance + * The emulator instance + * @param painter + * the painter used to draw the images + */ + private SWTRemoteDisplay createMainDisplay(Composite parent, IAndroidSkin skin, + IAndroidEmulatorInstance instance, ISWTPainter painter) + + { + // gets and reads the configuration files of the VNC session + IPropertiesFileHandler handler = IAndroidSkin.DEFAULT_PROPS_HANDLER; + String configFile = IAndroidSkin.DEFAULT_VNC_CONFIG_FILE; + + VNCConfiguration config = new VNCConfiguration(configFile, handler); + final SWTRemoteDisplay mainDisplay; + mainDisplay = + new SWTRemoteDisplayImg(parent, config.getConfigurationProperties(), handler, + painter); + + mainDisplay.setBackground(new Color(mainDisplay.getDisplay(), 0, 0, 0)); + mainDisplay.setLayout(new FillLayout()); + + return mainDisplay; + } + + /** + * Forces the refreshing of the menu elements. + */ + @Override + protected void refreshMenuElements() + { + IViewSite viewSite = getViewSite(); + + // Update the radio button selection in the zoom menu + ICommandService service = (ICommandService) viewSite.getService(ICommandService.class); + service.refreshElements(IHandlerConstants.CHANGE_EMULATOR_ORIENTATION_COMMAND, null); + service.refreshElements(IHandlerConstants.CHANGE_EMULATOR_ZOOM_COMMAND, null); + + } + +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AndroidViewData.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AndroidViewData.java new file mode 100644 index 0000000..1116bb4 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/AndroidViewData.java @@ -0,0 +1,167 @@ +/* +* 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.ui.view; + +import static com.motorola.studio.android.common.log.StudioLogger.debug; + +import java.io.File; +import java.util.Collection; + +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.swt.events.DisposeListener; + +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.SkinFramework; +import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite; +import com.motorola.studio.android.emulator.ui.controls.RemoteCLIDisplay; + +/** + * AndroidViewData: this class is responsible for having the information about Main Display, + * CLI Display and the composite where these displays will be drawn. + */ +public class AndroidViewData +{ + /** + * Implementation of the + */ + private IAndroidSkin skin; + + /** + * Composite that shows the contents of the mobile main display + */ + private SWTRemoteDisplay mainDisplay; + + /** + * Composite that shows the contents of the mobile CLI display + */ + private RemoteCLIDisplay cliDisplay; + + /** + * Composite for view's components. + */ + private IAndroidComposite composite; + + /** + * Dispose listener of this instance + */ + private DisposeListener disposeListener; + + /** + * Loads the Android emulator skin of the given the AVD/instance + * @param instance whose skin will be loaded + * @throws SkinException if it is no possible to load the skin + */ + public synchronized void loadSkin(IAndroidEmulatorInstance instance) throws SkinException + { + String skinId = instance.getSkinId(); + SkinFramework skinFw = new SkinFramework(); + File skinPath = instance.getSkinPath(); + skin = skinFw.getSkinById(skinId, skinPath); + Collection<String> layoutNames = skin.getAvailableLayouts(); + String currentLayout = instance.getCurrentLayout(); + if ((currentLayout == null) && (!layoutNames.isEmpty())) + { + String firstLayout = layoutNames.iterator().next(); + instance.setCurrentLayout(firstLayout); + debug("The skin has multiple layouts. Setting " + firstLayout + " as the current one."); + } + + } + + /** + * Retrieves the loaded IAndroidSkin. + * Returns null if no skin is loaded. + * @return + */ + public synchronized IAndroidSkin getSkin() + { + return skin; + } + + /** + * Gets the dispose listener + * @return dispose listener + */ + DisposeListener getDisposeListener() + { + return disposeListener; + } + + /** + * Sets the dispose listener + * @param disposeListener dispose listener + */ + void setDisposeListener(DisposeListener disposeListener) + { + this.disposeListener = disposeListener; + } + + /** + * Gets Main Display + * @return main display + */ + public SWTRemoteDisplay getMainDisplay() + { + return mainDisplay; + } + + /** + * Sets main Display + * @param mainDisplay main display + */ + void setMainDisplay(SWTRemoteDisplay mainDisplay) + { + this.mainDisplay = mainDisplay; + } + + /** + * Gets CLI Display + * @return CLI display + */ + RemoteCLIDisplay getCliDisplay() + { + return cliDisplay; + } + + /** + * Sets CLI Display + * @param cliDisplay CLI display + */ + void setCliDisplay(RemoteCLIDisplay cliDisplay) + { + this.cliDisplay = cliDisplay; + } + + /** + * Gets view composite + * @return composite + */ + public IAndroidComposite getComposite() + { + return composite; + } + + /** + * Sets view composite + * @param composite composite + */ + void setComposite(IAndroidComposite composite) + { + this.composite = composite; + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/LayoutContributionItem.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/LayoutContributionItem.java new file mode 100644 index 0000000..640ee37 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/LayoutContributionItem.java @@ -0,0 +1,143 @@ +/* +* 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.ui.view; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.CompoundContributionItem; +import org.eclipse.ui.menus.CommandContributionItem; +import org.eclipse.ui.menus.CommandContributionItemParameter; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.ui.handlers.IHandlerConstants; + +/** + * This class builds dynamically the Emulator Layouts submenu from the Android Emulator and + * Main Display views + **/ +public class LayoutContributionItem extends CompoundContributionItem +{ + // Layout dynamic constants + public static final String ANDROID_VIEW_LAYOUT_DYNAMIC_ID = "androidView.layout.dynamic"; + + public static final String MAIN_DISPLAY_VIEW_LAYOUT_DYNAMIC_ID = + "mainDisplayView.layout.dynamic"; + + /* + * (non-Javadoc) + * @see org.eclipse.ui.actions.CompoundContributionItem#getContributionItems() + */ + @Override + protected IContributionItem[] getContributionItems() + { + IContributionItem[] itemsArray; + + AbstractAndroidView view = null; + String viewId = null; + if (getId().equals(ANDROID_VIEW_LAYOUT_DYNAMIC_ID)) + { + viewId = AndroidView.ANDROID_VIEW_ID; + view = (AbstractAndroidView) EclipseUtils.getActiveView(viewId); + } + else if (getId().equals(MAIN_DISPLAY_VIEW_LAYOUT_DYNAMIC_ID)) + { + viewId = MainDisplayView.EMULATOR_MAIN_DISPLAY_VIEW_ID; + view = (AbstractAndroidView) EclipseUtils.getActiveView(viewId); + } + + if (view != null) + { + IAndroidEmulatorInstance instance = AbstractAndroidView.getActiveInstance(); + IAndroidSkin skin = view.getSkin(instance); + if (skin != null) + { + Collection<String> layoutNames = skin.getAvailableLayouts(); + itemsArray = new IContributionItem[layoutNames.size()]; + populateContributionList(itemsArray, layoutNames, viewId); + } + else + { + itemsArray = new IContributionItem[1]; + populateWithEmpty(itemsArray); + } + } + else + { + itemsArray = new IContributionItem[1]; + populateWithEmpty(itemsArray); + } + + return itemsArray; + } + + /** + * Populates the array with a command item per layout name + * + * @param itemsArray The array to be populated + * @param layoutNames The items to be included at the array + * @param viewId The view that is active at the moment + */ + @SuppressWarnings({ + "rawtypes", "unchecked" + }) + private void populateContributionList(IContributionItem[] itemsArray, + Collection<String> layoutNames, String viewId) + { + int i = 0; + for (String layoutName : layoutNames) + { + Map params = new HashMap(); + params.put(IHandlerConstants.ACTIVE_VIEW_PARAMETER, viewId); + + String id = EmulatorPlugin.PLUGIN_ID + ".layoutcmd." + layoutName; + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + CommandContributionItemParameter itemParam = + new CommandContributionItemParameter(window, id, + IHandlerConstants.CHANGE_EMULATOR_ORIENTATION_COMMAND, params, null, + null, null, layoutName, null, null, + CommandContributionItem.STYLE_RADIO, null, true); + itemsArray[i++] = new CommandContributionItem(itemParam); + } + } + + /** + * Populates the array with a single disabled command, indicating that there + * are no layouts to choose + * + * @param itemsArray The array to be populated + */ + @SuppressWarnings("rawtypes") + private void populateWithEmpty(IContributionItem[] itemsArray) + { + String id = EmulatorPlugin.PLUGIN_ID + ".emptylayout"; + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + CommandContributionItemParameter itemParam = + new CommandContributionItemParameter(window, id, + IHandlerConstants.CHANGE_EMULATOR_ORIENTATION_COMMAND, new HashMap(), null, + null, null, EmulatorNLS.UI_LayoutContributionItem_NoLayoutsAvailable, null, + null, CommandContributionItem.STYLE_RADIO, null, false); + itemsArray[0] = new CommandContributionItem(itemParam); + } +} diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/MainDisplayView.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/MainDisplayView.java new file mode 100644 index 0000000..2280b21 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/ui/view/MainDisplayView.java @@ -0,0 +1,224 @@ +/* +* 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.ui.view; + +import static com.motorola.studio.android.common.log.StudioLogger.error; + +import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle; +import org.eclipse.sequoyah.vnc.vncviewer.config.IPropertiesFileHandler; +import org.eclipse.sequoyah.vnc.vncviewer.config.VNCConfiguration; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.ISWTPainter; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.SWTRemoteDisplay; +import org.eclipse.sequoyah.vnc.vncviewer.graphics.swt.img.SWTRemoteDisplayImg; +import org.eclipse.sequoyah.vnc.vncviewer.network.VNCProtocolData; +import org.eclipse.sequoyah.vnc.vncviewer.registry.VNCProtocolRegistry; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.ui.IViewSite; +import org.eclipse.ui.commands.ICommandService; + +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.core.exception.InstanceStopException; +import com.motorola.studio.android.emulator.core.exception.SkinException; +import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance; +import com.motorola.studio.android.emulator.core.skin.IAndroidSkin; +import com.motorola.studio.android.emulator.core.skin.ISkinKeyXmlTags; +import com.motorola.studio.android.emulator.i18n.EmulatorNLS; +import com.motorola.studio.android.emulator.ui.IUIHelpConstants; +import com.motorola.studio.android.emulator.ui.controls.maindisplay.MainDisplayComposite; +import com.motorola.studio.android.emulator.ui.handlers.IHandlerConstants; + +/** + * This class represents the view that shows the Main Display (without skin) + * of the emulator to the end user. + */ +public class MainDisplayView extends AbstractAndroidView +{ + + /** + * The Android Emulator Main Display View view ID + */ + public static final String EMULATOR_MAIN_DISPLAY_VIEW_ID = + EmulatorPlugin.PLUGIN_ID + ".mainDisplayView"; + + /** + * @see com.motorola.studio.android.emulator.ui.view.AbstractAndroidView#getViewId() + */ + @Override + public String getViewId() + { + return EMULATOR_MAIN_DISPLAY_VIEW_ID; + } + + /** + * @see com.motorola.studio.android.emulator.ui.view.AbstractAndroidView#getHelpId() + */ + @Override + protected String getHelpId() + { + return IUIHelpConstants.EMULATOR_VIEW_MAIN_DISPLAY_HELP; + } + + /** + * Creates the scrolled composite, the detached composite and the main + * display related to emulator. + * + * @param tab the tab item that will hold the graphical elements that + * represents the emulator + * @param instance the emulator instance + * @param emulatorData the object to be defined with the elements created. + */ + @Override + protected void createWidgets(TabItem tab, final IAndroidEmulatorInstance instance, + final AndroidViewData tabData) + { + try + { + tabData.loadSkin(instance); + IAndroidSkin skin = tabData.getSkin(); + + ProtocolHandle handle = instance.getProtocolHandle(); + VNCProtocolData data = VNCProtocolRegistry.getInstance().get(handle); + if (data != null) + { + + int baseWidth = + skin.getSkinBean(instance.getCurrentLayout()).getSkinPropertyValue( + ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_WIDTH); + int baseHeight = + skin.getSkinBean(instance.getCurrentLayout()).getSkinPropertyValue( + ISkinKeyXmlTags.SKIN_INTERNAL_VIEW_HEIGHT); + + ScrolledComposite scrolledComposite = + new ScrolledComposite(tab.getParent(), SWT.H_SCROLL | SWT.V_SCROLL + | SWT.BACKGROUND); + + final MainDisplayComposite composite = + new MainDisplayComposite(scrolledComposite, SWT.BACKGROUND, baseWidth, + baseHeight, instance); + composite.setLayout(new FillLayout()); + + final SWTRemoteDisplay mainDisplay = + createMainDisplay(composite, skin, instance, (ISWTPainter) data + .getVncPainter()); + composite.setSize(baseWidth, baseHeight); + + scrolledComposite.setContent(composite); + tab.setControl(scrolledComposite); + + scrolledComposite.addDisposeListener(new DisposeListener() + { + public void widgetDisposed(DisposeEvent e) + { + composite.dispose(); + } + }); + + tabData.setCliDisplay(null); + tabData.setComposite(composite); + tabData.setMainDisplay(mainDisplay); + } + else + { + error("The protocol object set in the device instance is not supported. Stopping the emulator instance..."); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.ERR_AndroidView_ProtocolImplementerNotSupported); + + try + { + instance.stop(true); + } + catch (InstanceStopException e) + { + error("Error while running service for stopping virtual machine"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_General_CannotRunStopService); + } + } + } + catch (SkinException e) + { + error("The skin associated to this instance (" + instance.getName() + + ") is not installed or is corrupted."); + EclipseUtils.showErrorDialog(e); + + try + { + instance.stop(true); + } + catch (InstanceStopException e1) + { + error("Error while running service for stopping virtual machine"); + EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error, + EmulatorNLS.EXC_General_CannotRunStopService); + } + } + + } + + /** + * Creates the main display used by this instance + * + * @param parent The parent composite where to place the widgets + * @param skin The object containing skin information used to draw the widgets + * @param instance the emulator instance + * @param painter the painter used to draw the image + */ + private SWTRemoteDisplay createMainDisplay(Composite parent, IAndroidSkin skin, + IAndroidEmulatorInstance instance, ISWTPainter painter) + { + + // gets and reads the configuration files of the VNC session + IPropertiesFileHandler handler; + String configFile; + + handler = IAndroidSkin.DEFAULT_PROPS_HANDLER; + configFile = IAndroidSkin.DEFAULT_VNC_CONFIG_FILE; + + VNCConfiguration config = new VNCConfiguration(configFile, handler); + + SWTRemoteDisplay mainDisplay = + new SWTRemoteDisplayImg(parent, config.getConfigurationProperties(), handler, + painter); + mainDisplay.setBackground(new Color(mainDisplay.getDisplay(), 0, 0, 0)); + mainDisplay.setLayout(new FillLayout()); + + return mainDisplay; + } + + /** + * Forces the refreshing of the menu elements. + */ + @Override + protected void refreshMenuElements() + { + IViewSite viewSite = getViewSite(); + + // Update the radio button selection in the zoom menu + ICommandService service = (ICommandService) viewSite.getService(ICommandService.class); + service.refreshElements(IHandlerConstants.CHANGE_EMULATOR_ZOOM_COMMAND, null); + service.refreshElements(IHandlerConstants.CHANGE_EMULATOR_ORIENTATION_COMMAND, null); + + } + +}
\ No newline at end of file diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator10/StartAndroidEmulatorLogic.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator10/StartAndroidEmulatorLogic.java new file mode 100644 index 0000000..089dd06 --- /dev/null +++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator10/StartAndroidEmulatorLogic.java @@ -0,0 +1,117 @@ +/* +* 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.emulator10; + +import java.io.File; +import java.util.Collection; +import java.util.LinkedList; + +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; + +import com.motorola.studio.android.adt.SdkUtils; +import com.motorola.studio.android.common.utilities.PluginUtils; +import com.motorola.studio.android.emulator.EmulatorPlugin; +import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic; +import com.motorola.studio.android.emulator.logic.ConnectVncLogic; +import com.motorola.studio.android.emulator.logic.ForwardVncPortLogic; +import com.motorola.studio.android.emulator.logic.IAndroidLogic; +import com.motorola.studio.android.emulator.logic.IAndroidLogicInstance; +import com.motorola.studio.android.emulator.logic.StartEmulatorProcessLogic; +import com.motorola.studio.android.emulator.logic.StartVncServerLogic; +import com.motorola.studio.android.emulator.logic.TransferFilesLogic; + +public class StartAndroidEmulatorLogic extends AbstractStartAndroidEmulatorLogic +{ + @SuppressWarnings("incomplete-switch") + @Override + public Collection<IAndroidLogic> getLogicCollection(IAndroidLogicInstance instance, + LogicMode mode) + { + // When starting, all steps must be done. When restarting, only VNC server launch + // step will be performed. + Collection<IAndroidLogic> logicCollection = new LinkedList<IAndroidLogic>(); + + switch (mode) + { + case START_MODE: + logicCollection.add(new StartEmulatorProcessLogic()); + break; + case TRANSFER_AND_CONNECT_VNC: + logicCollection.add(createTransferFilesLogic()); + logicCollection.add(new ForwardVncPortLogic()); + StartVncServerLogic startVncServerLogic = createStartVncServerLogic(); + logicCollection.add(startVncServerLogic); + logicCollection.add(getConnectVncClientLogic(startVncServerLogic)); + break; + case RESTART_VNC_SERVER: + logicCollection.add(createTransferFilesLogic()); + logicCollection.add(new ForwardVncPortLogic()); + logicCollection.add(createStartVncServerLogic()); + break; + } + + return logicCollection; + } + + private String getResourceDir() + { + String resDir = "res"; + if (SdkUtils.isOphoneSDK()) + { + resDir = "res_OPhone"; + } + + return resDir; + } + + protected IAndroidLogic createTransferFilesLogic() + { + File localDirParent = PluginUtils.getPluginInstallationPath(EmulatorPlugin.getDefault()); + File localDir = new File(localDirParent, getResourceDir()); + + TransferFilesLogic transferLogic = new TransferFilesLogic(); + transferLogic.setLocalDir(localDir.getAbsolutePath()); + transferLogic.setRemoteDir("/data/local"); + transferLogic.addFilename("fbvncserver"); + return transferLogic; + } + + protected StartVncServerLogic createStartVncServerLogic() + { + StartVncServerLogic logic = new StartVncServerLogic(); + logic.addRemoteCommand("chmod 700 /data/local/fbvncserver"); + logic.addRemoteCommand("/data/local/fbvncserver"); + return logic; + } + + protected IAndroidLogic getConnectVncClientLogic(StartVncServerLogic startVncServerLogic) + { + final ConnectVncLogic startVncClientLogic = new ConnectVncLogic(); + + startVncServerLogic.addVncServerJobListener(new JobChangeAdapter() + { + @Override + public void done(IJobChangeEvent ijobchangeevent) + { + startVncClientLogic.setVncServerDoneEvent(ijobchangeevent); + } + }); + + return startVncClientLogic; + } + +} |