From b7327498cb4cdf4b5b20d9dc646fbd0e54b83b0f Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 28 Apr 2009 23:41:40 -0700 Subject: [PATCH] ADT #1820114: Provide a quick UI to edit AVD. This is temporary and will be replaced once we get the standalone AVD UI. --- .../icons/avd_manager.png | Bin 0 -> 135 bytes .../com.android.ide.eclipse.adt/plugin.xml | 16 + .../android/ide/eclipse/adt/AdtPlugin.java | 2 - .../adt/wizards/actions/AvdManagerAction.java | 34 + .../avdmanager/AvdManagerListPage.java | 632 ++++++++++++++++++ .../wizards/avdmanager/AvdManagerWizard.java | 100 +++ .../manifest/model/UiClassAttributeNode.java | 2 +- .../app/src/com/android/sdkmanager/Main.java | 22 +- .../com/android/sdklib/avd/AvdManager.java | 82 ++- .../src/com/android/sdkuilib/AvdSelector.java | 2 +- 10 files changed, 852 insertions(+), 40 deletions(-) create mode 100755 tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png create mode 100755 tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java create mode 100755 tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java create mode 100755 tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png b/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png new file mode 100755 index 0000000000000000000000000000000000000000..7dbbbb6a4b6d8e6d3d862fc36531df6b469133b9 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`ex5FlAr`&KDF^ueJr+n$0D>bY z6`hRR?8I_5S$~r~wDR)dlYFh`U0QN04z6Gf>2Q0a_oLq+?fNy@7fvMwjAz1Pz8DlU gnlW=oHZ5XjXpvSd|9FS57icnrr>mdKI;Vst0Q2K6DgXcg literal 0 HcmV?d00001 diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 6022a2027..dc6062d95 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -538,6 +538,22 @@ label="Refactor"> + + + + diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 8ef0f2caf..936f47cb8 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -1386,8 +1386,6 @@ public class AdtPlugin extends AbstractUIPlugin { /** * Pings the usage start server. - * @param pluginName the name of the plugin to appear in the stats - * @param pluginVersion the {@link Version} of the plugin. */ private void pingUsageServer() { // get the version of the plugin diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java new file mode 100755 index 000000000..d3f328ee3 --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.wizards.actions; + +import com.android.ide.eclipse.adt.wizards.avdmanager.AvdManagerWizard; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ui.IWorkbenchWizard; + +/** + * Delegate for the toolbar/menu action "Android AVD Manager". + * It displays the Android AVD Manager. + */ +public class AvdManagerAction extends OpenWizardAction { + + @Override + protected IWorkbenchWizard instanciateWizard(IAction action) { + return new AvdManagerWizard(); + } +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java new file mode 100755 index 000000000..a7691ca3c --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.wizards.avdmanager; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener; +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.avd.AvdManager; +import com.android.sdklib.avd.AvdManager.AvdInfo; +import com.android.sdkuilib.AvdSelector; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.TreeMap; + +/** + * This is the single page of the {@link AvdManagerWizard} which provides the ability to display + * the AVDs and edit them quickly. + */ +class AvdManagerListPage extends WizardPage { + + private AvdSelector mAvdSelector; + private Button mDeleteButton; + private Button mRefreshButton; + private Text mCreateName; + private Combo mCreateTargetCombo; + private Text mCreateSdCard; + private Combo mCreateSkinCombo; + private Button mCreateForce; + private Button mCreateButton; + private HashSet mKnownAvdNames = new HashSet(); + private TreeMap mCurrentTargets = new TreeMap(); + private ITargetChangeListener mSdkTargetChangeListener; + + /** + * Constructs a new {@link AvdManagerListPage}. + *

+ * Called by {@link AvdManagerWizard#createMainPage()}. + */ + protected AvdManagerListPage(String pageName) { + super(pageName); + setPageComplete(false); + } + + /** + * Called by the parent Wizard to create the UI for this Wizard Page. + * + * {@inheritDoc} + * + * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) + */ + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setFont(parent.getFont()); + + initializeDialogUnits(parent); + + composite.setLayout(new GridLayout(1, false /*makeColumnsEqualWidth*/)); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + createAvdGroup(composite); + createCreateGroup(composite); + registerSdkChangeListener(); + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + // Update state the first time + reloadAvdList(); + reloadTargetCombo(); + validatePage(); + } + + private void registerSdkChangeListener() { + + mSdkTargetChangeListener = new ITargetChangeListener() { + public void onProjectTargetChange(IProject changedProject) { + // Ignore + } + + public void onTargetsLoaded() { + // Update the AVD list, since the SDK change will influence the "good" avd list + reloadAvdList(); + // Update the sdk target combo with the new targets + reloadTargetCombo(); + validatePage(); + } + }; + AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener); + + } + + @Override + public void dispose() { + if (mSdkTargetChangeListener != null) { + AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener); + mSdkTargetChangeListener = null; + } + + super.dispose(); + } + + // --- UI creation --- + + /** + * Creates the AVD selector and refresh & delete buttons. + */ + private void createAvdGroup(Composite parent) { + int col = 0; + + final Composite grid2 = new Composite(parent, SWT.NONE); + grid2.setLayout(new GridLayout(2, false /*makeColumnsEqualWidth*/)); + grid2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + Label label = new Label(grid2, SWT.NONE); + label.setText("List of existing Android Virtual Devices:"); + label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false)); + + mRefreshButton = new Button(grid2, SWT.PUSH); + mRefreshButton.setText("Refresh"); + mRefreshButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + mRefreshButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + reloadAvdList(); + } + }); + + mAvdSelector = new AvdSelector(parent, null); + mAvdSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + updateDeleteButton(); + } + }); + + mDeleteButton = new Button(parent, SWT.PUSH); + mDeleteButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false)); + mDeleteButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onDelete(); + } + }); + updateDeleteButton(); + } + + private void updateDeleteButton() { + AvdInfo selected = mAvdSelector.getFirstSelected(); + mDeleteButton.setText(String.format("Delete %s...", + selected == null ? "" : selected.getName())); //$NON-NLS-1$ + mDeleteButton.pack(); + mDeleteButton.setEnabled(selected != null); + } + + /** + * Creates the "Create" group + */ + private void createCreateGroup(Composite parent) { + + Group grid = new Group(parent, SWT.SHADOW_ETCHED_IN); + grid.setLayout(new GridLayout(4, false /*makeColumnsEqualWidth*/)); + grid.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + grid.setFont(parent.getFont()); + grid.setText("Create AVD"); + + // first line + + Label label = new Label(grid, SWT.NONE); + label.setText("Name"); + + mCreateName = new Text(grid, SWT.BORDER); + mCreateName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mCreateName.addModifyListener(new CreateNameModifyListener()); + + label = new Label(grid, SWT.NONE); + label.setText("Target"); + + mCreateTargetCombo = new Combo(grid, SWT.READ_ONLY | SWT.DROP_DOWN); + mCreateTargetCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mCreateTargetCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + reloadSkinCombo(); + validatePage(); + } + }); + + // second line + + label = new Label(grid, SWT.NONE); + label.setText("SDCard"); + + ValidateListener validateListener = new ValidateListener(); + + mCreateSdCard = new Text(grid, SWT.BORDER); + mCreateSdCard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mCreateSdCard.addModifyListener(validateListener); + + label = new Label(grid, SWT.NONE); + label.setText("Skin"); + + mCreateSkinCombo = new Combo(grid, SWT.READ_ONLY | SWT.DROP_DOWN); + mCreateSkinCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + // dummies for alignment + label = new Label(grid, SWT.NONE); + label = new Label(grid, SWT.NONE); + + mCreateForce = new Button(grid, SWT.CHECK); + mCreateForce.setText("Force"); + mCreateForce.setEnabled(false); + mCreateForce.addSelectionListener(validateListener); + + mCreateButton = new Button(grid, SWT.PUSH); + mCreateButton.setText("Create AVD"); + mCreateButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + mCreateButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + onCreate(); + } + }); + } + + /** + * Callback when the AVD name is changed. + * Enables the force checkbox if the name is a duplicate. + */ + private class CreateNameModifyListener implements ModifyListener { + + public void modifyText(ModifyEvent e) { + String name = mCreateName.getText().trim(); + if (mKnownAvdNames.contains(name)) { + mCreateForce.setEnabled(true); + } else { + mCreateForce.setEnabled(false); + mCreateForce.setSelection(false); + } + validatePage(); + } + } + + private class ValidateListener extends SelectionAdapter implements ModifyListener { + + public void modifyText(ModifyEvent e) { + validatePage(); + } + + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + validatePage(); + } + } + + private void reloadTargetCombo() { + + String selected = null; + int index = mCreateTargetCombo.getSelectionIndex(); + if (index >= 0) { + selected = mCreateTargetCombo.getItem(index); + } + + mCurrentTargets.clear(); + mCreateTargetCombo.removeAll(); + + boolean found = false; + index = -1; + + Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + for (IAndroidTarget target : sdk.getTargets()) { + String name = String.format("%s - %s", + target.getName(), + target.getApiVersionName()); + mCurrentTargets.put(name, target); + mCreateTargetCombo.add(name); + if (!found) { + index++; + found = name.equals(selected); + } + } + } + + mCreateTargetCombo.setEnabled(mCurrentTargets.size() > 0); + + if (found) { + mCreateTargetCombo.select(index); + } + + reloadSkinCombo(); + } + + private void reloadSkinCombo() { + String selected = null; + int index = mCreateSkinCombo.getSelectionIndex(); + if (index >= 0) { + selected = mCreateSkinCombo.getItem(index); + } + + mCreateSkinCombo.removeAll(); + mCreateSkinCombo.setEnabled(false); + + index = mCreateTargetCombo.getSelectionIndex(); + if (index >= 0) { + + String targetName = mCreateTargetCombo.getItem(index); + + boolean found = false; + IAndroidTarget target = mCurrentTargets.get(targetName); + if (target != null) { + mCreateSkinCombo.add(String.format("Default (%s)", target.getDefaultSkin())); + + index = -1; + for (String skin : target.getSkins()) { + mCreateSkinCombo.add(skin); + if (!found) { + index++; + found = skin.equals(selected); + } + } + + mCreateSkinCombo.setEnabled(true); + + if (found) { + mCreateSkinCombo.select(index); + } else { + mCreateSkinCombo.select(0); // default + } + } + } + } + + /** + * Validates the fields, displays errors and warnings. + * Enables the finish button if there are no errors. + *

+ * Not really used here yet. Keep as a placeholder. + */ + private void validatePage() { + String error = null; + String warning = null; + + + // Validate AVD name + String avdName = mCreateName.getText().trim(); + boolean hasAvdName = avdName.length() > 0; + if (hasAvdName && !AvdManager.RE_AVD_NAME.matcher(avdName).matches()) { + error = String.format( + "AVD name '%1$s' contains invalid characters. Allowed characters are: %2$s", + avdName, AvdManager.CHARS_AVD_NAME); + } + + // Validate target + if (hasAvdName && error == null && mCreateTargetCombo.getSelectionIndex() < 0) { + error = "A target must be selected in order to create an AVD."; + } + + // Validate SDCard path or value + if (error == null) { + String sdName = mCreateSdCard.getText().trim(); + + if (sdName.length() > 0 && + !new File(sdName).isFile() && + !AvdManager.SDCARD_SIZE_PATTERN.matcher(sdName).matches()) { + error = "SD Card must be either a file path or a size such as 128K or 64M."; + } + } + + // Check for duplicate AVD name + if (hasAvdName && error == null) { + if (mKnownAvdNames.contains(avdName) && !mCreateForce.getSelection()) { + error = String.format( + "The AVD name '%s' is already used. " + + "Check \"Force\" if you really want to create a new AVD with that name and delete the previous one.", + avdName); + } + } + + // Validate the create button + boolean can_create = hasAvdName && error == null; + if (can_create) { + can_create &= mCreateTargetCombo.getSelectionIndex() >= 0; + } + mCreateButton.setEnabled(can_create); + + // -- update UI + setPageComplete(true); + if (error != null) { + setMessage(error, WizardPage.ERROR); + } else if (warning != null) { + setMessage(warning, WizardPage.WARNING); + } else { + setErrorMessage(null); + setMessage(null); + } + } + + /** + * Reloads the AVD list in the AVD selector. + * Tries to preserve the selection. + */ + private void reloadAvdList() { + AvdInfo selected = mAvdSelector.getFirstSelected(); + + AvdManager avdm = getAvdManager(); + + AvdInfo[] avds = avdm == null ? null : avdm.getValidAvds(); + mAvdSelector.setAvds(avds, null /*filter*/); + + // Keep the list of known AVD names to check if they exist quickly. however + // use the list of all AVDs, including broken ones (unless we don't know their + // name). + mKnownAvdNames.clear(); + if (avdm != null) { + for (AvdInfo avd : avdm.getAllAvds()) { + String name = avd.getName(); + if (name != null) { + mKnownAvdNames.add(name); + } + } + } + + mAvdSelector.setSelection(selected); + } + + /** + * Triggered when the user selects the "delete" button. + * Deletes the currently selected AVD, if any. + */ + private void onDelete() { + AvdInfo avdInfo = mAvdSelector.getFirstSelected(); + AvdManager avdm = getAvdManager(); + if (avdInfo == null || avdm == null) { + return; + } + + // Confirm you want to delete this AVD + if (!AdtPlugin.displayPrompt("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()))) { + return; + } + + SdkLog log = new SdkLog(String.format("Result of deleting AVD '%s':", avdInfo.getName())); + + boolean success = avdm.deleteAvd(avdInfo, log); + + log.display(success); + reloadAvdList(); + updateDeleteButton(); + } + + /** + * Triggered when the user selects the "create" button. + */ + private void onCreate() { + String avdName = mCreateName.getText().trim(); + String sdName = mCreateSdCard.getText().trim(); + int targetIndex = mCreateTargetCombo.getSelectionIndex(); + int skinIndex = mCreateSkinCombo.getSelectionIndex(); + boolean force = mCreateForce.getSelection(); + AvdManager avdm = getAvdManager(); + + if (avdm == null || + avdName.length() == 0 || + targetIndex < 0) { + return; + } + + String targetName = mCreateTargetCombo.getItem(targetIndex); + IAndroidTarget target = mCurrentTargets.get(targetName); + if (target == null) { + return; + } + + String skinName = null; + if (skinIndex > 0) { + // index 0 is the default, we don't use it + skinName = mCreateSkinCombo.getItem(skinIndex); + } + + SdkLog log = new SdkLog(String.format("Result of creating AVD '%s':", avdName)); + + File avdFolder; + try { + avdFolder = new File( + AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD, + avdName + AvdManager.AVD_FOLDER_EXTENSION); + } catch (AndroidLocationException e) { + AdtPlugin.logAndPrintError(e, null /*tag*/, + "AndroidLocation.getFolder failed"); //$NON-NLS-1$ + return; + } + + ISdkLog oldLog = null; + boolean success = false; + try { + // Temporarily change the AvdManager's logger for ours, since the API no longer + // takes a logger argument. + // TODO revisit this later. See comments in AvdManager#mSdkLog. + oldLog = avdm.setSdkLog(log); + + AvdInfo avdInfo = avdm.createAvd( + avdFolder, + avdName, + target, + skinName, + sdName, + null, // hardwareConfig, + force); + + success = avdInfo != null; + + } finally { + avdm.setSdkLog(oldLog); + } + + log.display(success); + + if (success) { + // clear the name field on success + mCreateName.setText(""); //$NON-NLS-1$ + } + + reloadAvdList(); + } + + /** + * Collects all log from the AVD action and displays it in a dialog. + */ + private class SdkLog implements ISdkLog { + + final ArrayList logMessages = new ArrayList(); + private final String mMessage; + + public SdkLog(String message) { + mMessage = message; + } + + public void error(Throwable throwable, String errorFormat, Object... arg) { + if (errorFormat != null) { + logMessages.add(String.format("Error: " + errorFormat, arg)); + } + + if (throwable != null) { + logMessages.add(throwable.getMessage()); + } + } + + public void warning(String warningFormat, Object... arg) { + logMessages.add(String.format("Warning: " + warningFormat, arg)); + } + + public void printf(String msgFormat, Object... arg) { + logMessages.add(String.format(msgFormat, arg)); + } + + /** + * Displays the log if anything was captured. + */ + public void display(boolean success) { + if (logMessages.size() > 0) { + StringBuilder sb = new StringBuilder(mMessage + "\n"); + for (String msg : logMessages) { + sb.append('\n'); + sb.append(msg); + } + if (success) { + AdtPlugin.displayWarning("Android Virtual Devices Manager", sb.toString()); + } else { + AdtPlugin.displayError("Android Virtual Devices Manager", sb.toString()); + } + } + } + } + + /** + * Returns the current AVD Manager or null if none has been created yet. + * This can happen when the SDK hasn't finished loading or the manager failed to + * parse the AVD directory. + */ + private AvdManager getAvdManager() { + Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + return sdk.getAvdManager(); + } + return null; + } +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java new file mode 100755 index 000000000..20cad5a5b --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.wizards.avdmanager; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +/** + * The "AVD Manager Wizard" provides a quick way to edit AVDs. + *

+ * The wizard has one page, {@link AvdManagerListPage}, used to display and edit the AVDs. + * In fact the whole UI is not really a wizard. It has just been implemented that way + * to get something quick out of the door. We'll need to revisit this when we implement + * the final standalone AVD Manager UI and this Wizard will go away. + */ +public class AvdManagerWizard extends Wizard implements INewWizard { + + private static final String PROJECT_LOGO_LARGE = "android_large"; //$NON-NLS-1$ + + protected static final String MAIN_PAGE_NAME = "avdManagerListPage"; //$NON-NLS-1$ + + private AvdManagerListPage mMainPage; + + public void init(IWorkbench workbench, IStructuredSelection selection) { + setHelpAvailable(false); // TODO have help + setWindowTitle("Android Virtual Devices Manager"); + setImageDescriptor(); + + mMainPage = createMainPage(); + mMainPage.setTitle("Android Virtual Devices Manager"); + mMainPage.setDescription("Displays existing Android Virtual Devices. Lets you create new ones or delete existing ones."); + } + + /** + * Creates the wizard page. + *

+ * Please do NOT override this method. + *

+ * This is protected so that it can be overridden by unit tests. + * However the contract of this class is private and NO ATTEMPT will be made + * to maintain compatibility between different versions of the plugin. + */ + protected AvdManagerListPage createMainPage() { + return new AvdManagerListPage(MAIN_PAGE_NAME); + } + + // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- + // + // The Wizard class implements most defaults and boilerplate code needed by + // IWizard + + /** + * Adds pages to this wizard. + */ + @Override + public void addPages() { + addPage(mMainPage); + } + + /** + * Performs any actions appropriate in response to the user having pressed + * the Finish button, or refuse if finishing now is not permitted: here, it does nothing. + * + * @return True + */ + @Override + public boolean performFinish() { + return true; + } + + /** + * Returns an image descriptor for the wizard logo. + */ + private void setImageDescriptor() { + ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + } + +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java index c872b6f31..f5c0cfa71 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java @@ -463,7 +463,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode { * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source folders of * the specified project. * @param project the project - * @param b + * @param include_containers True to include containers * @return an array of IPackageFragmentRoot. */ private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project, diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java b/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java index 35d9bb165..1e6b58537 100644 --- a/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java +++ b/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java @@ -35,7 +35,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Pattern; /** * Main class for the 'android' application. @@ -50,11 +49,6 @@ class Main { private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" }; private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" }; - - /** Regex used to validate characters that compose an AVD name. */ - private final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+"); - /** List of valid characters for an AVD name. Used for display purposes. */ - private final static String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -"; /** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */ @@ -515,10 +509,10 @@ class Main { String avdName = mSdkCommandLine.getParamName(); - if (!RE_AVD_NAME.matcher(avdName).matches()) { + if (!AvdManager.RE_AVD_NAME.matcher(avdName).matches()) { errorAndExit( "AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s", - avdName, CHARS_AVD_NAME); + avdName, AvdManager.CHARS_AVD_NAME); return; } @@ -593,18 +587,6 @@ class Main { hardwareConfig, removePrevious); - if (newAvdInfo != null && - oldAvdInfo != null && - !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) { - mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath()); - // Remove the old data directory - File dir = new File(oldAvdInfo.getPath()); - avdManager.recursiveDelete(dir); - dir.delete(); - // Remove old AVD info from manager - avdManager.removeAvd(oldAvdInfo); - } - } catch (AndroidLocationException e) { errorAndExit(e.getMessage()); } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java index 23b2f26b6..041e8b563 100644 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java @@ -125,7 +125,13 @@ public final class AvdManager { /** * Pattern for matching SD Card sizes, e.g. "4K" or "16M". */ - private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?"); + public final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]"); + + /** Regex used to validate characters that compose an AVD name. */ + public final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+"); + + /** List of valid characters for an AVD name. Used for display purposes. */ + public final static String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -"; /** An immutable structure describing an Android Virtual Device. */ public static final class AvdInfo { @@ -301,15 +307,38 @@ public final class AvdManager { private final ArrayList mAllAvdList = new ArrayList(); private AvdInfo[] mValidAvdList; private AvdInfo[] mBrokenAvdList; - private ISdkLog mSdkLog; private final SdkManager mSdk; + /** + * TODO remove this field. Each caller should give its own logger to the methods + * here instead of relying on a global logger. Otherwise that means that each + * caller needs to re-instantiate this just to change the logger, which is plain + * ugly. + * + * We'll revisit this in the final AVD Manager UI for donut. + */ + private ISdkLog mSdkLog; + public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException { mSdk = sdk; mSdkLog = sdkLog; buildAvdList(mAllAvdList); } + /** + * Changes the current {@link ISdkLog}. + * + * This method should not exist. See comments for {@link #mSdkLog}. + * + * @return The {@link ISdkLog} that was currently being used. + * @see #mSdkLog + */ + public ISdkLog setSdkLog(ISdkLog sdkLog) { + ISdkLog oldLogger = mSdkLog; + mSdkLog = sdkLog; + return oldLogger; + } + /** * Returns all the existing AVDs. * @return a newly allocated array containing all the AVDs. @@ -405,14 +434,17 @@ public final class AvdManager { /** * Creates a new AVD. It is expected that there is no existing AVD with this name already. + * * @param avdFolder the data folder for the AVD. It will be created as needed. * @param name the name of the AVD * @param target the target of the AVD * @param skinName the name of the skin. Can be null. Must have been verified by caller. * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to - * an existing sdcard image or a sdcard size (\d+, \d+K, \dM). - * @param hardwareConfig the hardware setup for the AVD + * an existing sdcard image or a sdcard size (\d+, \d+K, \dM). + * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults. * @param removePrevious If true remove any previous files. + * @return The new {@link AvdInfo} in case of success (which has just been added to the + * internal list) or null in case of failure. */ public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target, String skinName, String sdcard, Map hardwareConfig, @@ -482,7 +514,7 @@ public final class AvdManager { } // Now the skin. - if (skinName == null) { + if (skinName == null || skinName.length() == 0) { skinName = target.getDefaultSkin(); } @@ -505,7 +537,7 @@ public final class AvdManager { values.put(AVD_INI_SKIN_NAME, skinName); } - if (sdcard != null) { + if (sdcard != null && sdcard.length() > 0) { File sdcardFile = new File(sdcard); if (sdcardFile.isFile()) { // sdcard value is an external sdcard, so we put its path into the config.ini @@ -571,15 +603,33 @@ public final class AvdManager { } // create the AvdInfo object, and add it to the list - AvdInfo avdInfo = new AvdInfo(name, avdFolder.getAbsolutePath(), target.hashString(), + AvdInfo newAvdInfo = new AvdInfo(name, + avdFolder.getAbsolutePath(), + target.hashString(), target, values); + + AvdInfo oldAvdInfo = getAvd(name, false /*validAvdOnly*/); synchronized (mAllAvdList) { - mAllAvdList.add(avdInfo); + if (oldAvdInfo != null && removePrevious) { + mAllAvdList.remove(oldAvdInfo); + } + mAllAvdList.add(newAvdInfo); mValidAvdList = mBrokenAvdList = null; } - - return avdInfo; + + if (removePrevious && + newAvdInfo != null && + oldAvdInfo != null && + !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) { + mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath()); + // Remove the old data directory + File dir = new File(oldAvdInfo.getPath()); + recursiveDelete(dir); + dir.delete(); + } + + return newAvdInfo; } catch (AndroidLocationException e) { if (mSdkLog != null) { mSdkLog.error(e, null); @@ -724,8 +774,9 @@ public final class AvdManager { * these operations fail. * * @param avdInfo the information on the AVD to delete + * @return True if the AVD was deleted with no error. */ - public void deleteAvd(AvdInfo avdInfo, ISdkLog log) { + public boolean deleteAvd(AvdInfo avdInfo, ISdkLog log) { try { boolean error = false; @@ -758,6 +809,7 @@ public final class AvdManager { avdInfo.getName()); } else { log.printf("AVD '%1$s' deleted.\n", avdInfo.getName()); + return true; } } catch (AndroidLocationException e) { @@ -765,6 +817,7 @@ public final class AvdManager { } catch (IOException e) { log.error(e, null); } + return false; } /** @@ -905,10 +958,8 @@ public final class AvdManager { * Parses an AVD .ini file to create an {@link AvdInfo}. * * @param path The path to the AVD .ini file - * @param acceptError When false, an AVD that fails to load will be discarded and null will be - * returned. When true, such an AVD will be returned with an error description. - * @return A new {@link AvdInfo} or null if the file is not valid or null if the AVD is not - * valid and acceptError is false. + * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is + * valid or not. */ private AvdInfo parseAvdInfo(File path) { Map map = SdkManager.parsePropertyFile(path, mSdkLog); @@ -1012,7 +1063,6 @@ public final class AvdManager { * @param toolLocation The path to the mksdcard tool. * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}. * @param location The path of the new sdcard image file to generate. - * @param log The logger object, to report errors. * @return True if the sdcard could be created. */ private boolean createSdCard(String toolLocation, String size, String location) { diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java index 20c211ba7..729741b67 100644 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java @@ -164,7 +164,7 @@ public final class AvdSelector { * If the selection is actually changed, this will invoke the selection listener * (if any) with a null event. * - * @param target the target to be selection + * @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) {