From 6f04045072aba987f0648ed020592ff4c6816fe2 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 9 Jul 2009 15:37:09 -0400 Subject: [PATCH] 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. --- .../internal/launch/AndroidLaunchController.java | 2 +- .../adt/internal/launch/DeviceChooserDialog.java | 1 + .../adt/internal/launch/EmulatorConfigTab.java | 4 +- .../internal/repository/AvdManagerPage.java | 5 +- .../internal/repository/UpdaterWindowImpl.java | 1 + .../{repository => tasks}/ProgressDialog.java | 2 +- .../{repository => tasks}/ProgressTask.java | 4 +- .../{repository => tasks}/ProgressTaskFactory.java | 4 +- .../internal/widgets/AvdCreationDialog.java | 1 - .../sdkuilib/internal/widgets/AvdSelector.java | 170 +++++++++++++++++++-- 10 files changed, 173 insertions(+), 21 deletions(-) rename sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/{repository => tasks}/ProgressDialog.java (96%) rename sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/{repository => tasks}/ProgressTask.java (95%) rename sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/{repository => tasks}/ProgressTaskFactory.java (88%) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java index 6de260129..aad181289 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java @@ -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. * Both must 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) { // read the lines as they come. if null is returned, it's diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java index 6813d0b9e..d2fe5ae23 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java @@ -411,6 +411,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener offsetComp.setLayout(layout); mPreferredAvdSelector = new AvdSelector(offsetComp, + mSdk.getSdkLocation(), mSdk.getAvdManager(), new NonRunningAvdFilter(), DisplayMode.SIMPLE_SELECTION); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java index 2378248ed..74d4c4bf6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java @@ -195,7 +195,9 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { // 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 // been changed in between. - mPreferredAvdSelector = new AvdSelector(offsetComp, null /* avd manager */, + mPreferredAvdSelector = new AvdSelector(offsetComp, + Sdk.getCurrent().getSdkLocation(), + null /* avd manager */, DisplayMode.SIMPLE_CHECK); mPreferredAvdSelector.setTableHeightHint(100); mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java index d69ce5f42..ccfd27607 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java @@ -52,7 +52,10 @@ public class AvdManagerPage extends Composite implements ISdkListener { Label label = new Label(parent, SWT.NONE); 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 diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java index 5c47b7cab..e36bd6c8e 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java @@ -23,6 +23,7 @@ import com.android.sdklib.internal.repository.RepoSource; import com.android.sdklib.internal.repository.RepoSources; import com.android.sdklib.repository.SdkRepository; import com.android.sdkuilib.internal.repository.icons.ImageFactory; +import com.android.sdkuilib.internal.tasks.ProgressTaskFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressDialog.java similarity index 96% rename from sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java rename to sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressDialog.java index fe73ac564..12ef2cc34 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdkuilib.internal.repository; +package com.android.sdkuilib.internal.tasks; import com.android.sdklib.internal.repository.ITaskMonitor; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java similarity index 95% rename from sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java rename to sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java index 1ec728701..9ed8a01d6 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java @@ -14,7 +14,7 @@ * 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.ITaskMonitor; @@ -26,7 +26,7 @@ import org.eclipse.swt.widgets.Shell; /** * 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; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskFactory.java similarity index 88% rename from sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java rename to sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskFactory.java index 08f5485aa..c90df26e5 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskFactory.java @@ -14,7 +14,7 @@ * 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.ITaskFactory; @@ -25,7 +25,7 @@ import org.eclipse.swt.widgets.Shell; * An {@link ITaskFactory} that creates a new {@link ProgressTask} dialog * for each new task. */ -public class ProgressTaskFactory implements ITaskFactory { +public final class ProgressTaskFactory implements ITaskFactory { private final Shell mShell; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java index 9ef76e2ac..8804c702b 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java @@ -19,7 +19,6 @@ package com.android.sdkuilib.internal.widgets; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkManager; import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.internal.avd.AvdManager.AvdInfo; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java index ab02b8d77..8826b4c0e 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java @@ -20,10 +20,14 @@ 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.icons.ImageFactory; +import com.android.sdkuilib.internal.tasks.ProgressTask; import com.android.sdkuilib.repository.UpdaterWindow; 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.TableItem; +import java.io.BufferedReader; +import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; import java.util.ArrayList; /** * The AVD selector is a table that is added to the given parent composite. *

- * To use, create it using {@link #AvdSelector(Composite, AvdManager, DisplayMode)} then - * call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)} - * and finally use {@link #getSelected()} to retrieve the selection. + * 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; @@ -66,6 +73,7 @@ public final class AvdSelector { private final DisplayMode mDisplayMode; private AvdManager mAvdManager; + private final String mOsSdkPath; private Table mTable; private Button mDeleteButton; @@ -74,16 +82,20 @@ public final class AvdSelector { private Button mRefreshButton; private Button mManagerButton; private Button mUpdateButton; + 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; + /** * The display mode of the AVD Selector. */ @@ -170,15 +182,20 @@ public final class AvdSelector { * {@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 extraAction When non-null, displays an extra action button. * @param displayMode The display mode ({@link DisplayMode}). + * + * TODO: pass an ISdkLog and use it when reloading, starting the emulator, etc. */ public AvdSelector(Composite parent, + String osSdkPath, AvdManager manager, IAvdFilter filter, DisplayMode displayMode) { + mOsSdkPath = osSdkPath; mAvdManager = manager; mTargetFilter = filter; 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); padding.setLayoutData(new GridData(GridData.FILL_VERTICAL)); @@ -327,9 +355,11 @@ public final class AvdSelector { * @param manager the AVD manager. * @param displayMode The display mode ({@link DisplayMode}). */ - public AvdSelector(Composite parent, AvdManager manager, + public AvdSelector(Composite parent, + String osSdkPath, + AvdManager manager, 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}). */ public AvdSelector(Composite parent, + String osSdkPath, AvdManager manager, IAndroidTarget filter, DisplayMode displayMode) { - this(parent, manager, new TargetBasedFilter(filter), displayMode); + this(parent, osSdkPath, manager, new TargetBasedFilter(filter), displayMode); } /** * Sets the table grid layout data. @@ -373,7 +404,6 @@ public final class AvdSelector { * This must be called from the UI thread. * * @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 reload is * false. */ @@ -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() { if (mIsEnabled == false) { mDetailsButton.setEnabled(false); + mStartButton.setEnabled(false); + if (mDeleteButton != null) { mDeleteButton.setEnabled(false); } @@ -748,13 +780,20 @@ public final class AvdSelector { } } else { 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) { - mDeleteButton.setEnabled(selection != null); + mDeleteButton.setEnabled(hasSelection); } if (mUpdateButton != null) { - mUpdateButton.setEnabled(selection != null && + mUpdateButton.setEnabled(hasSelection && + selection != null && 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. } + 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 list = new ArrayList(); + 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 must 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. */ -- 2.11.0