/*
* Copyright (C) 2009 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.android.sdkuilib.internal.widgets;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.NullSdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdkuilib.internal.repository.SettingsController;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.tasks.ProgressTask;
import com.android.sdkuilib.repository.UpdaterWindow;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
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.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Formatter;
import java.util.Locale;
/**
* The AVD selector is a table that is added to the given parent composite.
* <p/>
* After using one of the constructors, call {@link #setSelection(AvdInfo)},
* {@link #setSelectionListener(SelectionListener)} and finally use
* {@link #getSelected()} to retrieve the selection.
*/
public final class AvdSelector {
private static int NUM_COL = 2;
private final DisplayMode mDisplayMode;
private AvdManager mAvdManager;
private final String mOsSdkPath;
private Table mTable;
private Button mDeleteButton;
private Button mDetailsButton;
private Button mNewButton;
private Button mEditButton;
private Button mRefreshButton;
private Button mManagerButton;
private Button mRepairButton;
private Button mStartButton;
private SelectionListener mSelectionListener;
private IAvdFilter mTargetFilter;
/** Defaults to true. Changed by the {@link #setEnabled(boolean)} method to represent the
* "global" enabled state on this composite. */
private boolean mIsEnabled = true;
private ImageFactory mImageFactory;
private Image mOkImage;
private Image mBrokenImage;
private Image mInvalidImage;
private SettingsController mController;
private final ISdkLog mSdkLog;
/**
* The display mode of the AVD Selector.
*/
public static enum DisplayMode {
/**
* Manager mode. Invalid AVDs are displayed. Buttons to create/delete AVDs
*/
MANAGER,
/**
* Non manager mode. Only valid AVDs are displayed. Cannot create/delete AVDs, but
* there is a button to open the AVD Manager.
* In the "check" selection mode, checkboxes are displayed on each line
* and {@link AvdSelector#getSelected()} returns the line that is checked
* even if it is not the currently selected line. Only one line can
* be checked at once.
*/
SIMPLE_CHECK,
/**
* Non manager mode. Only valid AVDs are displayed. Cannot create/delete AVDs, but
* there is a button to open the AVD Manager.
* In the "select" selection mode, there are no checkboxes and
* {@link AvdSelector#getSelected()} returns the line currently selected.
* Only one line can be selected at once.
*/
SIMPLE_SELECTION,
}
/**
* A filter to control the whether or not an AVD should be displayed by the AVD Selector.
*/
public interface IAvdFilter {
/**
* Called before {@link #accept(AvdInfo)} is called for any AVD.
*/
void prepare();
/**
* Called to decided whether an AVD should be displayed.
* @param avd the AVD to test.
* @return true if the AVD should be displayed.
*/
boolean accept(AvdInfo avd);
/**
* Called after {@link #accept(AvdInfo)} has been called on all the AVDs.
*/
void cleanup();
}
/**
* Internal implementation of {@link IAvdFilter} to filter out the AVDs that are not
* running an image compatible with a specific target.
*/
private final static class TargetBasedFilter implements IAvdFilter {
private final IAndroidTarget mTarget;
TargetBasedFilter(IAndroidTarget target) {
mTarget = target;
}
public void prepare() {
// nothing to prepare
}
public boolean accept(AvdInfo avd) {
if (avd != null) {
return mTarget.canRunOn(avd.getTarget());
}
return false;
}
public void cleanup() {
// nothing to clean up
}
}
/**
* Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
* by a {@link IAndroidTarget}.
* <p/>Only the {@link AvdInfo} able to run application developed for the given
* {@link IAndroidTarget} will be displayed.
*
* @param parent The parent composite where the selector will be added.
* @param osSdkPath The SDK root path. When not null, enables the start button to start
* an emulator on a given AVD.
* @param manager the AVD manager.
* @param filter When non-null, will allow filtering the AVDs to display.
* @param displayMode The display mode ({@link DisplayMode}).
* @param sdkLog The logger. Cannot be null.
*/
public AvdSelector(Composite parent,
String osSdkPath,
AvdManager manager,
IAvdFilter filter,
DisplayMode displayMode,
ISdkLog sdkLog) {
mOsSdkPath = osSdkPath;
mAvdManager = manager;
mTargetFilter = filter;
mDisplayMode = displayMode;
mSdkLog = sdkLog;
// get some bitmaps.
mImageFactory = new ImageFactory(parent.getDisplay());
mOkImage = mImageFactory.getImageByName("accept_icon16.png");
mBrokenImage = mImageFactory.getImageByName("broken_16.png");
mInvalidImage = mImageFactory.getImageByName("reject_icon16.png");
// Layout has 2 columns
Composite group = new Composite(parent, SWT.NONE);
GridLayout gl;
group.setLayout(gl = new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
gl.marginHeight = gl.marginWidth = 0;
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());
group.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent arg0) {
mImageFactory.dispose();
}
});
int style = SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER;
if (displayMode == DisplayMode.SIMPLE_CHECK) {
style |= SWT.CHECK;
}
mTable = new Table(group, style);
mTable.setHeaderVisible(true);
mTable.setLinesVisible(false);
setTableHeightHint(0);
Composite buttons = new Composite(group, SWT.NONE);
buttons.setLayout(gl = new GridLayout(1, false /*makeColumnsEqualWidth*/));
gl.marginHeight = gl.marginWidth = 0;
buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
buttons.setFont(group.getFont());
if (displayMode == DisplayMode.MANAGER) {
mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mNewButton.setText("New...");
mNewButton.setToolTipText("Creates a new AVD.");
mNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onNew();
}
});
mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mEditButton.setText("Edit...");
mEditButton.setToolTipText("Edit an existing AVD.");
mEditButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onEdit();
}
});
mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDeleteButton.setText("Delete...");
mDeleteButton.setToolTipText("Deletes the selected AVD.");
mDeleteButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onDelete();
}
});
mRepairButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mRepairButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mRepairButton.setText("Repair...");
mRepairButton.setToolTipText("Repairs the selected AVD.");
mRepairButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onRepair();
}
});
Label l = new Label(buttons, SWT.SEPARATOR | SWT.HORIZONTAL);
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
}
mDetailsButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mDetailsButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDetailsButton.setText("Details...");
mDetailsButton.setToolTipText("Diplays details of the selected AVD.");
mDetailsButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onDetails();
}
});
mStartButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mStartButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mStartButton.setText("Start...");
mStartButton.setToolTipText("Starts the selected AVD.");
mStartButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onStart();
}
});
Composite padding = new Composite(buttons, SWT.NONE);
padding.setLayoutData(new GridData(GridData.FILL_VERTICAL));
mRefreshButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mRefreshButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mRefreshButton.setText("Refresh");
mRefreshButton.setToolTipText("Reloads the list of AVD.\nUse this if you create AVDs from the command line.");
mRefreshButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
refresh(true);
}
});
if (displayMode != DisplayMode.MANAGER) {
mManagerButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mManagerButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mManagerButton.setText("Manager...");
mManagerButton.setToolTipText("Launches the AVD manager.");
mManagerButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onManager();
}
});
} else {
Composite legend = new Composite(group, SWT.NONE);
legend.setLayout(gl = new GridLayout(4, false /*makeColumnsEqualWidth*/));
gl.marginHeight = gl.marginWidth = 0;
legend.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true, false,
NUM_COL, 1));
legend.setFont(group.getFont());
new Label(legend, SWT.NONE).setImage(mOkImage);
new Label(legend, SWT.NONE).setText("A valid Android Virtual Device.");
new Label(legend, SWT.NONE).setImage(mBrokenImage);
new Label(legend, SWT.NONE).setText(
"A repairable Android Virtual Device.");
new Label(legend, SWT.NONE).setImage(mInvalidImage);
Label l = new Label(legend, SWT.NONE);
l.setText("An Android Virtual Device that failed to load. Click 'Details' to see the error.");
GridData gd;
l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
}
// create the table columns
final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
column0.setText("AVD Name");
final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
column1.setText("Target Name");
final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
column2.setText("Platform");
final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
column3.setText("API Level");
adjustColumnsWidth(mTable, column0, column1, column2, column3);
setupSelectionListener(mTable);
fillTable(mTable);
setEnabled(true);
}
/**
* Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
*
* @param parent The parent composite where the selector will be added.
* @param manager the AVD manager.
* @param displayMode The display mode ({@link DisplayMode}).
* @param sdkLog The logger. Cannot be null.
*/
public AvdSelector(Composite parent,
String osSdkPath,
AvdManager manager,
DisplayMode displayMode,
ISdkLog sdkLog) {
this(parent, osSdkPath, manager, (IAvdFilter)null /* filter */, displayMode, sdkLog);
}
/**
* Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
* by an {@link IAndroidTarget}.
* <p/>Only the {@link AvdInfo} able to run applications developed for the given
* {@link IAndroidTarget} will be displayed.
*
* @param parent The parent composite where the selector will be added.
* @param manager the AVD manager.
* @param filter Only shows the AVDs matching this target (must not be null).
* @param displayMode The display mode ({@link DisplayMode}).
* @param sdkLog The logger. Cannot be null.
*/
public AvdSelector(Composite parent,
String osSdkPath,
AvdManager manager,
IAndroidTarget filter,
DisplayMode displayMode,
ISdkLog sdkLog) {
this(parent, osSdkPath, manager, new TargetBasedFilter(filter), displayMode, sdkLog);
}
/**
* Sets an optional SettingsController.
* @param controller the controller.
*/
public void setSettingsController(SettingsController controller) {
mController = controller;
}
/**
* Sets the table grid layout data.
*
* @param heightHint If > 0, the height hint is set to the requested value.
*/
public void setTableHeightHint(int heightHint) {
GridData data = new GridData();
if (heightHint > 0) {
data.heightHint = heightHint;
}
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
mTable.setLayoutData(data);
}
/**
* Refresh the display of Android Virtual Devices.
* Tries to keep the selection.
* <p/>
* This must be called from the UI thread.
*
* @param reload if true, the AVD manager will reload the AVD from the disk.
* @return false if the reloading failed. This is always true if <var>reload</var> is
* <code>false</code>.
*/
public boolean refresh(boolean reload) {
if (reload) {
try {
mAvdManager.reloadAvds(NullSdkLog.getLogger());
} catch (AndroidLocationException e) {
return false;
}
}
AvdInfo selected = getSelected();
fillTable(mTable);
setSelection(selected);
return true;
}
/**
* Sets a new AVD manager
* This does not refresh the display. Call {@link #refresh(boolean)} to do so.
* @param manager the AVD manager.
*/
public void setManager(AvdManager manager) {
mAvdManager = manager;
}
/**
* Sets a new AVD filter.
* This does not refresh the display. Call {@link #refresh(boolean)} to do so.
* @param filter An IAvdFilter. If non-null, this will filter out the AVD to not display.
*/
public void setFilter(IAvdFilter filter) {
mTargetFilter = filter;
}
/**
* Sets a new Android Target-based AVD filter.
* This does not refresh the display. Call {@link #refresh(boolean)} to do so.
* @param target An IAndroidTarget. If non-null, only AVD whose target are compatible with the
* filter target will displayed an available for selection.
*/
public void setFilter(IAndroidTarget target) {
if (target != null) {
mTargetFilter = new TargetBasedFilter(target);
} else {
mTargetFilter = null;
}
}
/**
* Sets a selection listener. Set it to null to remove it.
* The listener will be called <em>after</em> this table processed its selection
* events so that the caller can see the updated state.
* <p/>
* The event's item contains a {@link TableItem}.
* The {@link TableItem#getData()} contains an {@link IAndroidTarget}.
* <p/>
* It is recommended that the caller uses the {@link #getSelected()} method instead.
* <p/>
* The default behavior for double click (when not in {@link DisplayMode#SIMPLE_CHECK}) is to
* display the details of the selected AVD.<br>
* To disable it (when you provide your own double click action), set
* {@link SelectionEvent#doit} to false in
* {@link SelectionListener#widgetDefaultSelected(SelectionEvent)}
*
* @param selectionListener The new listener or null to remove it.
*/
public void setSelectionListener(SelectionListener selectionListener) {
mSelectionListener = selectionListener;
}
/**
* Sets the current target selection.
* <p/>
* If the selection is actually changed, this will invoke the selection listener
* (if any) with a null event.
*
* @param target the target to be selected. Use null to deselect everything.
* @return true if the target could be selected, false otherwise.
*/
public boolean setSelection(AvdInfo target) {
boolean found = false;
boolean modified = false;
int selIndex = mTable.getSelectionIndex();
int index = 0;
for (TableItem i : mTable.getItems()) {
if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
if ((AvdInfo) i.getData() == target) {
found = true;
if (!i.getChecked()) {
modified = true;
i.setChecked(true);
}
} else if (i.getChecked()) {
modified = true;
i.setChecked(false);
}
} else {
if ((AvdInfo) i.getData() == target) {
found = true;
if (index != selIndex) {
mTable.setSelection(index);
modified = true;
}
break;
}
index++;
}
}
if (modified && mSelectionListener != null) {
mSelectionListener.widgetSelected(null);
}
enableActionButtons();
return found;
}
/**
* Returns the currently selected item. In {@link DisplayMode#SIMPLE_CHECK} mode this will
* return the {@link AvdInfo} that is checked instead of the list selection.
*
* @return The currently selected item or null.
*/
public AvdInfo getSelected() {
if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
for (TableItem i : mTable.getItems()) {
if (i.getChecked()) {
return (AvdInfo) i.getData();
}
}
} else {
int selIndex = mTable.getSelectionIndex();
if (selIndex >= 0) {
return (AvdInfo) mTable.getItem(selIndex).getData();
}
}
return null;
}
/**
* Enables the receiver if the argument is true, and disables it otherwise.
* A disabled control is typically not selectable from the user interface
* and draws with an inactive or "grayed" look.
*
* @param enabled the new enabled state.
*/
public void setEnabled(boolean enabled) {
// We can only enable widgets if the AVD Manager is defined.
mIsEnabled = enabled && mAvdManager != null;
mTable.setEnabled(mIsEnabled);
mRefreshButton.setEnabled(mIsEnabled);
if (mNewButton != null) {
mNewButton.setEnabled(mIsEnabled);
}
if (mManagerButton != null) {
mManagerButton.setEnabled(mIsEnabled);
}
enableActionButtons();
}
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Adds a listener to adjust the columns width when the parent is resized.
* <p/>
* If we need something more fancy, we might want to use this:
* http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
*/
private void adjustColumnsWidth(final Table table,
final TableColumn column0,
final TableColumn column1,
final TableColumn column2,
final TableColumn column3) {
// Add a listener to resize the column to the full width of the table
table.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = table.getClientArea();
column0.setWidth(r.width * 25 / 100); // 25%
column1.setWidth(r.width * 45 / 100); // 45%
column2.setWidth(r.width * 15 / 100); // 15%
column3.setWidth(r.width * 15 / 100); // 15%
}
});
}
/**
* Creates a selection listener that will check or uncheck the whole line when
* double-clicked (aka "the default selection").
*/
private void setupSelectionListener(final Table table) {
// Add a selection listener that will check/uncheck items when they are double-clicked
table.addSelectionListener(new SelectionListener() {
/**
* Handles single-click selection on the table.
* {@inheritDoc}
*/
public void widgetSelected(SelectionEvent e) {
if (e.item instanceof TableItem) {
TableItem i = (TableItem) e.item;
enforceSingleSelection(i);
}
if (mSelectionListener != null) {
mSelectionListener.widgetSelected(e);
}
enableActionButtons();
}
/**
* Handles double-click selection on the table.
* Note that the single-click handler will probably already have been called.
*
* On double-click, <em>always</em> check the table item.
*
* {@inheritDoc}
*/
public void widgetDefaultSelected(SelectionEvent e) {
if (e.item instanceof TableItem) {
TableItem i = (TableItem) e.item;
if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
i.setChecked(true);
}
enforceSingleSelection(i);
}
// whether or not we display details. default: true when not in SIMPLE_CHECK mode.
boolean showDetails = mDisplayMode != DisplayMode.SIMPLE_CHECK;
if (mSelectionListener != null) {
mSelectionListener.widgetDefaultSelected(e);
showDetails &= e.doit; // enforce false in SIMPLE_CHECK
}
if (showDetails) {
onDetails();
}
enableActionButtons();
}
/**
* To ensure single selection, uncheck all other items when this one is selected.
* This makes the chekboxes act as radio buttons.
*/
private void enforceSingleSelection(TableItem item) {
if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
if (item.getChecked()) {
Table parentTable = item.getParent();
for (TableItem i2 : parentTable.getItems()) {
if (i2 != item && i2.getChecked()) {
i2.setChecked(false);
}
}
}
} else {
// pass
}
}
});
}
/**
* Fills the table with all AVD.
* The table columns are:
* <ul>
* <li>column 0: sdk name
* <li>column 1: sdk vendor
* <li>column 2: sdk api name
* <li>column 3: sdk version
* </ul>
*/
private void fillTable(final Table table) {
table.removeAll();
// get the AVDs
AvdInfo avds[] = null;
if (mAvdManager != null) {
if (mDisplayMode == DisplayMode.MANAGER) {
avds = mAvdManager.getAllAvds();
} else {
avds = mAvdManager.getValidAvds();
}
}
if (avds != null && avds.length > 0) {
Arrays.sort(avds, new Comparator<AvdInfo>() {
public int compare(AvdInfo o1, AvdInfo o2) {
return o1.compareTo(o2);
}
});
table.setEnabled(true);
if (mTargetFilter != null) {
mTargetFilter.prepare();
}
for (AvdInfo avd : avds) {
if (mTargetFilter == null || mTargetFilter.accept(avd)) {
TableItem item = new TableItem(table, SWT.NONE);
item.setData(avd);
item.setText(0, avd.getName());
if (mDisplayMode == DisplayMode.MANAGER) {
AvdStatus status = avd.getStatus();
item.setImage(0, status == AvdStatus.OK ? mOkImage :
isAvdRepairable(status) ? mBrokenImage : mInvalidImage);
}
IAndroidTarget target = avd.getTarget();
if (target != null) {
item.setText(1, target.getFullName());
item.setText(2, target.getVersionName());
item.setText(3, target.getVersion().getApiString());
} else {
item.setText(1, "?");
item.setText(2, "?");
item.setText(3, "?");
}
}
}
if (mTargetFilter != null) {
mTargetFilter.cleanup();
}
}
if (table.getItemCount() == 0) {
table.setEnabled(false);
TableItem item = new TableItem(table, SWT.NONE);
item.setData(null);
item.setText(0, "--");
item.setText(1, "No AVD available");
item.setText(2, "--");
item.setText(3, "--");
}
}
/**
* Returns the currently selected AVD in the table.
* <p/>
* Unlike {@link #getSelected()} this will always return the item being selected
* in the list, ignoring the check boxes state in {@link DisplayMode#SIMPLE_CHECK} mode.
*/
private AvdInfo getTableSelection() {
int selIndex = mTable.getSelectionIndex();
if (selIndex >= 0) {
return (AvdInfo) mTable.getItem(selIndex).getData();
}
return null;
}
/**
* Updates the enable state of the Details, Start, Delete and Update buttons.
*/
@SuppressWarnings("null")
private void enableActionButtons() {
if (mIsEnabled == false) {
mDetailsButton.setEnabled(false);
mStartButton.setEnabled(false);
if (mEditButton != null) {
mEditButton.setEnabled(false);
}
if (mDeleteButton != null) {
mDeleteButton.setEnabled(false);
}
if (mRepairButton != null) {
mRepairButton.setEnabled(false);
}
} else {
AvdInfo selection = getTableSelection();
boolean hasSelection = selection != null;
mDetailsButton.setEnabled(hasSelection);
mStartButton.setEnabled(mOsSdkPath != null &&
hasSelection &&
selection.getStatus() == AvdStatus.OK);
if (mEditButton != null) {
mEditButton.setEnabled(hasSelection);
}
if (mDeleteButton != null) {
mDeleteButton.setEnabled(hasSelection);
}
if (mRepairButton != null) {
mRepairButton.setEnabled(hasSelection && isAvdRepairable(selection.getStatus()));
}
}
}
private void onNew() {
AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(),
mAvdManager,
mImageFactory,
mSdkLog,
null);
if (dlg.open() == Window.OK) {
refresh(false /*reload*/);
}
}
private void onEdit() {
AvdInfo avdInfo = getTableSelection();
AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(),
mAvdManager,
mImageFactory,
mSdkLog,
avdInfo);
if (dlg.open() == Window.OK) {
refresh(false /*reload*/);
}
}
private void onDetails() {
AvdInfo avdInfo = getTableSelection();
AvdDetailsDialog dlg = new AvdDetailsDialog(mTable.getShell(), avdInfo);
dlg.open();
}
private void onDelete() {
final AvdInfo avdInfo = getTableSelection();
// get the current Display
final Display display = mTable.getDisplay();
// check if the AVD is running
if (avdInfo.isRunning()) {
display.asyncExec(new Runnable() {
public void run() {
Shell shell = display.getActiveShell();
MessageDialog.openError(shell,
"Delete Android Virtual Device",
String.format(
"The Android Virtual Device '%1$s' is currently running in an emulator and cannot be deleted.",
avdInfo.getName()));
}
});
return;
}
// Confirm you want to delete this AVD
final boolean[] result = new boolean[1];
display.syncExec(new Runnable() {
public void run() {
Shell shell = display.getActiveShell();
result[0] = MessageDialog.openQuestion(shell,
"Delete Android Virtual Device",
String.format(
"Please confirm that you want to delete the Android Virtual Device named '%s'. This operation cannot be reverted.",
avdInfo.getName()));
}
});
if (result[0] == false) {
return;
}
// log for this action.
ISdkLog log = mSdkLog;
if (log == null || log instanceof MessageBoxLog) {
// If the current logger is a message box, we use our own (to make sure
// to display errors right away and customize the title).
log = new MessageBoxLog(
String.format("Result of deleting AVD '%s':", avdInfo.getName()),
display,
false /*logErrorsOnly*/);
}
// delete the AVD
boolean success = mAvdManager.deleteAvd(avdInfo, log);
// display the result
if (log instanceof MessageBoxLog) {
((MessageBoxLog) log).displayResult(success);
}
if (success) {
refresh(false /*reload*/);
}
}
/**
* Repairs the selected AVD.
* <p/>
* For now this only supports fixing the wrong value in image.sysdir.*
*/
private void onRepair() {
final AvdInfo avdInfo = getTableSelection();
// get the current Display
final Display display = mTable.getDisplay();
// log for this action.
ISdkLog log = mSdkLog;
if (log == null || log instanceof MessageBoxLog) {
// If the current logger is a message box, we use our own (to make sure
// to display errors right away and customize the title).
log = new MessageBoxLog(
String.format("Result of updating AVD '%s':", avdInfo.getName()),
display,
false /*logErrorsOnly*/);
}
// delete the AVD
try {
mAvdManager.updateAvd(avdInfo, log);
// display the result
if (log instanceof MessageBoxLog) {
((MessageBoxLog) log).displayResult(true /* success */);
}
refresh(false /*reload*/);
} catch (IOException e) {
log.error(e, null);
if (log instanceof MessageBoxLog) {
((MessageBoxLog) log).displayResult(false /* success */);
}
}
}
private void onManager() {
// get the current Display
Display display = mTable.getDisplay();
// log for this action.
ISdkLog log = mSdkLog;
if (log == null || log instanceof MessageBoxLog) {
// If the current logger is a message box, we use our own (to make sure
// to display errors right away and customize the title).
log = new MessageBoxLog("Result of SDK Manager", display, true /*logErrorsOnly*/);
}
UpdaterWindow window = new UpdaterWindow(
mTable.getShell(),
log,
mAvdManager.getSdkManager().getLocation());
window.open();
refresh(true /*reload*/); // UpdaterWindow uses its own AVD manager so this one must reload.
if (log instanceof MessageBoxLog) {
((MessageBoxLog) log).displayResult(true);
}
}
private void onStart() {
AvdInfo avdInfo = getTableSelection();
if (avdInfo == null || mOsSdkPath == null) {
return;
}
AvdStartDialog dialog = new AvdStartDialog(mTable.getShell(), avdInfo, mOsSdkPath,
mController);
if (dialog.open() == Window.OK) {
String path = mOsSdkPath +
File.separator +
SdkConstants.OS_SDK_TOOLS_FOLDER +
SdkConstants.FN_EMULATOR;
final String avdName = avdInfo.getName();
// build the command line based on the available parameters.
ArrayList<String> list = new ArrayList<String>();
list.add(path);
list.add("-avd"); //$NON-NLS-1$
list.add(avdName);
if (dialog.hasWipeData()) {
list.add("-wipe-data"); //$NON-NLS-1$
}
if (dialog.hasSnapshot()) {
if (!dialog.hasSnapshotLaunch()) {
list.add("-no-snapshot-load");
}
if (!dialog.hasSnapshotSave()) {
list.add("-no-snapshot-save");
}
}
float scale = dialog.getScale();
if (scale != 0.f) {
// do the rounding ourselves. This is because %.1f will write .4899 as .4
scale = Math.round(scale * 100);
scale /= 100.f;
list.add("-scale"); //$NON-NLS-1$
// because the emulator expects English decimal values, don't use String.format
// but a Formatter.
Formatter formatter = new Formatter(Locale.US);
formatter.format("%.2f", scale); //$NON-NLS-1$
list.add(formatter.toString());
}
// convert the list into an array for the call to exec.
final String[] command = list.toArray(new String[list.size()]);
// launch the emulator
new ProgressTask(mTable.getShell(),
"Starting Android Emulator",
new ITask() {
public void run(ITaskMonitor monitor) {
try {
monitor.setDescription("Starting emulator for AVD '%1$s'",
avdName);
int n = 10;
monitor.setProgressMax(n);
Process process = Runtime.getRuntime().exec(command);
grabEmulatorOutput(process, monitor);
// This small wait prevents the dialog from closing too fast:
// When it works, the emulator returns immediately, even if
// no UI is shown yet. And when it fails (because the AVD is
// locked/running)
// if we don't have a wait we don't capture the error for
// some reason.
for (int i = 0; i < n; i++) {
try {
Thread.sleep(100);
monitor.incProgress(1);
} catch (InterruptedException e) {
// ignore
}
}
} catch (IOException e) {
monitor.setResult("Failed to start emulator: %1$s",
e.getMessage());
}
}
});
}
}
/**
* Get the stderr/stdout outputs of a process and return when the process is done.
* Both <b>must</b> be read or the process will block on windows.
* @param process The process to get the output from.
* @param monitor An {@link ITaskMonitor} to capture errors. Cannot be null.
*/
private void grabEmulatorOutput(final Process process, final ITaskMonitor monitor) {
// read the lines as they come. if null is returned, it's because the process finished
new Thread("emu-stderr") { //$NON-NLS-1$
@Override
public void run() {
// create a buffer to read the stderr output
InputStreamReader is = new InputStreamReader(process.getErrorStream());
BufferedReader errReader = new BufferedReader(is);
try {
while (true) {
String line = errReader.readLine();
if (line != null) {
monitor.setResult("%1$s", line); //$NON-NLS-1$
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
new Thread("emu-stdout") { //$NON-NLS-1$
@Override
public void run() {
InputStreamReader is = new InputStreamReader(process.getInputStream());
BufferedReader outReader = new BufferedReader(is);
try {
while (true) {
String line = outReader.readLine();
if (line != null) {
monitor.setResult("%1$s", line); //$NON-NLS-1$
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
}
private boolean isAvdRepairable(AvdStatus avdStatus) {
return avdStatus == AvdStatus.ERROR_IMAGE_DIR;
}
}