Merge change 6651 into donut

* changes:
  AVD Selector: Add a "Start" button to launch the selected AVD in a new emulator. Doesn't detect if the AVD is already running; instead prints the emulator error.
This commit is contained in:
Android (Google) Code Review
2009-07-09 12:53:30 -07:00
10 changed files with 173 additions and 21 deletions

View File

@@ -1521,7 +1521,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/** /**
* Get the stderr/stdout outputs of a process and return when the process is done. * 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. * Both <b>must</b> be read or the process will block on windows.
* @param process The process to get the ouput from * @param process The process to get the output from
*/ */
private void grabEmulatorOutput(final Process process) { private void grabEmulatorOutput(final Process process) {
// read the lines as they come. if null is returned, it's // read the lines as they come. if null is returned, it's

View File

@@ -411,6 +411,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
offsetComp.setLayout(layout); offsetComp.setLayout(layout);
mPreferredAvdSelector = new AvdSelector(offsetComp, mPreferredAvdSelector = new AvdSelector(offsetComp,
mSdk.getSdkLocation(),
mSdk.getAvdManager(), mSdk.getAvdManager(),
new NonRunningAvdFilter(), new NonRunningAvdFilter(),
DisplayMode.SIMPLE_SELECTION); DisplayMode.SIMPLE_SELECTION);

View File

@@ -195,7 +195,9 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
// create the selector with no manager, we'll reset the manager every time this is // create the selector with no manager, we'll reset the manager every time this is
// displayed to ensure we have the latest one (dialog is reused but SDK could have // displayed to ensure we have the latest one (dialog is reused but SDK could have
// been changed in between. // been changed in between.
mPreferredAvdSelector = new AvdSelector(offsetComp, null /* avd manager */, mPreferredAvdSelector = new AvdSelector(offsetComp,
Sdk.getCurrent().getSdkLocation(),
null /* avd manager */,
DisplayMode.SIMPLE_CHECK); DisplayMode.SIMPLE_CHECK);
mPreferredAvdSelector.setTableHeightHint(100); mPreferredAvdSelector.setTableHeightHint(100);
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {

View File

@@ -52,7 +52,10 @@ public class AvdManagerPage extends Composite implements ISdkListener {
Label label = new Label(parent, SWT.NONE); Label label = new Label(parent, SWT.NONE);
label.setText("List of existing Android Virtual Devices:"); label.setText("List of existing Android Virtual Devices:");
mAvdSelector = new AvdSelector(parent, mUpdaterData.getAvdManager(), DisplayMode.MANAGER); mAvdSelector = new AvdSelector(parent,
mUpdaterData.getOsSdkRoot(),
mUpdaterData.getAvdManager(),
DisplayMode.MANAGER);
} }
@Override @Override

View File

@@ -23,6 +23,7 @@ import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources; import com.android.sdklib.internal.repository.RepoSources;
import com.android.sdklib.repository.SdkRepository; import com.android.sdklib.repository.SdkRepository;
import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.tasks.ProgressTaskFactory;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.SashForm;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.sdkuilib.internal.repository; package com.android.sdkuilib.internal.tasks;
import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdklib.internal.repository.ITaskMonitor;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.sdkuilib.internal.repository; package com.android.sdkuilib.internal.tasks;
import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdklib.internal.repository.ITaskMonitor;
@@ -26,7 +26,7 @@ import org.eclipse.swt.widgets.Shell;
/** /**
* An {@link ITaskMonitor} that displays a {@link ProgressDialog}. * An {@link ITaskMonitor} that displays a {@link ProgressDialog}.
*/ */
class ProgressTask implements ITaskMonitor { public final class ProgressTask implements ITaskMonitor {
private static final double MAX_COUNT = 10000.0; private static final double MAX_COUNT = 10000.0;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.sdkuilib.internal.repository; package com.android.sdkuilib.internal.tasks;
import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskFactory; import com.android.sdklib.internal.repository.ITaskFactory;
@@ -25,7 +25,7 @@ import org.eclipse.swt.widgets.Shell;
* An {@link ITaskFactory} that creates a new {@link ProgressTask} dialog * An {@link ITaskFactory} that creates a new {@link ProgressTask} dialog
* for each new task. * for each new task.
*/ */
public class ProgressTaskFactory implements ITaskFactory { public final class ProgressTaskFactory implements ITaskFactory {
private final Shell mShell; private final Shell mShell;

View File

@@ -19,7 +19,6 @@ package com.android.sdkuilib.internal.widgets;
import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkManager; import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdklib.internal.avd.AvdManager.AvdInfo;

View File

@@ -20,10 +20,14 @@ import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog; import com.android.sdklib.ISdkLog;
import com.android.sdklib.NullSdkLog; import com.android.sdklib.NullSdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus; 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.icons.ImageFactory; import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.tasks.ProgressTask;
import com.android.sdkuilib.repository.UpdaterWindow; import com.android.sdkuilib.repository.UpdaterWindow;
import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.MessageDialog;
@@ -49,16 +53,19 @@ import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.TableItem;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* The AVD selector is a table that is added to the given parent composite. * The AVD selector is a table that is added to the given parent composite.
* <p/> * <p/>
* To use, create it using {@link #AvdSelector(Composite, AvdManager, DisplayMode)} then * After using one of the constructors, call {@link #setSelection(AvdInfo)},
* call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)} * {@link #setSelectionListener(SelectionListener)} and finally use
* and finally use {@link #getSelected()} to retrieve the selection. * {@link #getSelected()} to retrieve the selection.
*/ */
public final class AvdSelector { public final class AvdSelector {
private static int NUM_COL = 2; private static int NUM_COL = 2;
@@ -66,6 +73,7 @@ public final class AvdSelector {
private final DisplayMode mDisplayMode; private final DisplayMode mDisplayMode;
private AvdManager mAvdManager; private AvdManager mAvdManager;
private final String mOsSdkPath;
private Table mTable; private Table mTable;
private Button mDeleteButton; private Button mDeleteButton;
@@ -74,16 +82,20 @@ public final class AvdSelector {
private Button mRefreshButton; private Button mRefreshButton;
private Button mManagerButton; private Button mManagerButton;
private Button mUpdateButton; private Button mUpdateButton;
private Button mStartButton;
private SelectionListener mSelectionListener; private SelectionListener mSelectionListener;
private IAvdFilter mTargetFilter; 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 boolean mIsEnabled = true;
private ImageFactory mImageFactory; private ImageFactory mImageFactory;
private Image mOkImage; private Image mOkImage;
private Image mBrokenImage; private Image mBrokenImage;
/** /**
* The display mode of the AVD Selector. * The display mode of the AVD Selector.
*/ */
@@ -170,15 +182,20 @@ public final class AvdSelector {
* {@link IAndroidTarget} will be displayed. * {@link IAndroidTarget} will be displayed.
* *
* @param parent The parent composite where the selector will be added. * @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 manager the AVD manager.
* @param filter When non-null, will allow filtering the AVDs to display. * @param filter When non-null, will allow filtering the AVDs to display.
* @param extraAction When non-null, displays an extra action button.
* @param displayMode The display mode ({@link DisplayMode}). * @param displayMode The display mode ({@link DisplayMode}).
*
* TODO: pass an ISdkLog and use it when reloading, starting the emulator, etc.
*/ */
public AvdSelector(Composite parent, public AvdSelector(Composite parent,
String osSdkPath,
AvdManager manager, AvdManager manager,
IAvdFilter filter, IAvdFilter filter,
DisplayMode displayMode) { DisplayMode displayMode) {
mOsSdkPath = osSdkPath;
mAvdManager = manager; mAvdManager = manager;
mTargetFilter = filter; mTargetFilter = filter;
mDisplayMode = displayMode; mDisplayMode = displayMode;
@@ -265,6 +282,17 @@ public final class AvdSelector {
} }
}); });
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); Composite padding = new Composite(buttons, SWT.NONE);
padding.setLayoutData(new GridData(GridData.FILL_VERTICAL)); padding.setLayoutData(new GridData(GridData.FILL_VERTICAL));
@@ -327,9 +355,11 @@ public final class AvdSelector {
* @param manager the AVD manager. * @param manager the AVD manager.
* @param displayMode The display mode ({@link DisplayMode}). * @param displayMode The display mode ({@link DisplayMode}).
*/ */
public AvdSelector(Composite parent, AvdManager manager, public AvdSelector(Composite parent,
String osSdkPath,
AvdManager manager,
DisplayMode displayMode) { DisplayMode displayMode) {
this(parent, manager, (IAvdFilter)null /* filter */, displayMode); this(parent, osSdkPath, manager, (IAvdFilter)null /* filter */, displayMode);
} }
/** /**
@@ -344,10 +374,11 @@ public final class AvdSelector {
* @param displayMode The display mode ({@link DisplayMode}). * @param displayMode The display mode ({@link DisplayMode}).
*/ */
public AvdSelector(Composite parent, public AvdSelector(Composite parent,
String osSdkPath,
AvdManager manager, AvdManager manager,
IAndroidTarget filter, IAndroidTarget filter,
DisplayMode displayMode) { DisplayMode displayMode) {
this(parent, manager, new TargetBasedFilter(filter), displayMode); this(parent, osSdkPath, manager, new TargetBasedFilter(filter), displayMode);
} }
/** /**
* Sets the table grid layout data. * Sets the table grid layout data.
@@ -373,7 +404,6 @@ public final class AvdSelector {
* This must be called from the UI thread. * This must be called from the UI thread.
* *
* @param reload if true, the AVD manager will reload the AVD from the disk. * @param reload if true, the AVD manager will reload the AVD from the disk.
* @throws AndroidLocationException if reload the AVD failed.
* @return false if the reloading failed. This is always true if <var>reload</var> is * @return false if the reloading failed. This is always true if <var>reload</var> is
* <code>false</code>. * <code>false</code>.
*/ */
@@ -735,11 +765,13 @@ public final class AvdSelector {
} }
/** /**
* Updates the enable state of the Details, Delete and Update buttons. * Updates the enable state of the Details, Start, Delete and Update buttons.
*/ */
private void enableActionButtons() { private void enableActionButtons() {
if (mIsEnabled == false) { if (mIsEnabled == false) {
mDetailsButton.setEnabled(false); mDetailsButton.setEnabled(false);
mStartButton.setEnabled(false);
if (mDeleteButton != null) { if (mDeleteButton != null) {
mDeleteButton.setEnabled(false); mDeleteButton.setEnabled(false);
} }
@@ -748,13 +780,20 @@ public final class AvdSelector {
} }
} else { } else {
AvdInfo selection = getTableSelection(); AvdInfo selection = getTableSelection();
boolean hasSelection = selection != null;
mDetailsButton.setEnabled(hasSelection);
mStartButton.setEnabled(mOsSdkPath != null &&
hasSelection &&
selection != null &&
selection.getStatus() == AvdStatus.OK);
mDetailsButton.setEnabled(selection != null);
if (mDeleteButton != null) { if (mDeleteButton != null) {
mDeleteButton.setEnabled(selection != null); mDeleteButton.setEnabled(hasSelection);
} }
if (mUpdateButton != null) { if (mUpdateButton != null) {
mUpdateButton.setEnabled(selection != null && mUpdateButton.setEnabled(hasSelection &&
selection != null &&
selection.getStatus() == AvdStatus.ERROR_IMAGE_DIR); selection.getStatus() == AvdStatus.ERROR_IMAGE_DIR);
} }
} }
@@ -849,6 +888,113 @@ public final class AvdSelector {
refresh(true /*reload*/); // UpdaterWindow uses its own AVD manager so this one must reload. refresh(true /*reload*/); // UpdaterWindow uses its own AVD manager so this one must reload.
} }
private void onStart() {
AvdInfo avdInfo = getTableSelection();
if (avdInfo == null || mOsSdkPath == null) {
return;
}
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);
// 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 emualator 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 ISdkLog} to capture errors.
*/
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();
}
/** /**
* Collects all log from the AVD action and displays it in a dialog. * Collects all log from the AVD action and displays it in a dialog.
*/ */