/*
* 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;
}
}