/* * 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.io.FileWrapper; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.internal.avd.HardwareProperties; import com.android.sdklib.internal.avd.AvdManager.AvdConflict; import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.ui.GridDialog; import com.android.util.Pair; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.window.Window; 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.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Point; 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.Control; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Group; 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.Text; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; import java.util.regex.Matcher; /** * AVD creation or edit dialog. * * TODO: * - use SdkTargetSelector instead of Combo * - tooltips on widgets. * */ final class AvdCreationDialog extends GridDialog { private final AvdManager mAvdManager; private final TreeMap<String, IAndroidTarget> mCurrentTargets = new TreeMap<String, IAndroidTarget>(); private final Map<String, HardwareProperty> mHardwareMap; private final Map<String, String> mProperties = new HashMap<String, String>(); // a list of user-edited properties. private final ArrayList<String> mEditedProperties = new ArrayList<String>(); private final ImageFactory mImageFactory; private final ISdkLog mSdkLog; private final AvdInfo mEditAvdInfo; private Text mAvdName; private Combo mTargetCombo; private Button mSdCardSizeRadio; private Text mSdCardSize; private Combo mSdCardSizeCombo; private Text mSdCardFile; private Button mBrowseSdCard; private Button mSdCardFileRadio; private Button mSnapshotCheck; private Button mSkinListRadio; private Combo mSkinCombo; private Button mSkinSizeRadio; private Text mSkinSizeWidth; private Text mSkinSizeHeight; private TableViewer mHardwareViewer; private Button mDeleteHardwareProp; private Button mForceCreation; private Button mOkButton; private Label mStatusIcon; private Label mStatusLabel; private Composite mStatusComposite; /** * {@link VerifyListener} for {@link Text} widgets that should only contains numbers. */ private final VerifyListener mDigitVerifier = new VerifyListener() { public void verifyText(VerifyEvent event) { int count = event.text.length(); for (int i = 0 ; i < count ; i++) { char c = event.text.charAt(i); if (c < '0' || c > '9') { event.doit = false; return; } } } }; /** * Callback when the AVD name is changed. * When creating a new AVD, enables the force checkbox if the name is a duplicate. * When editing an existing AVD, it's OK for the name to match the existing AVD. */ private class CreateNameModifyListener implements ModifyListener { public void modifyText(ModifyEvent e) { String name = mAvdName.getText().trim(); if (mEditAvdInfo == null || !name.equals(mEditAvdInfo.getName())) { Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(name); if (conflict.getFirst() != AvdManager.AvdConflict.NO_CONFLICT) { // If we're changing the state from disabled to enabled, make sure // to uncheck the button, to force the user to voluntarily re-enforce it. // This happens when editing an existing AVD and changing the name from // the existing AVD to another different existing AVD. if (!mForceCreation.isEnabled()) { mForceCreation.setEnabled(true); mForceCreation.setSelection(false); } } else { mForceCreation.setEnabled(false); mForceCreation.setSelection(false); } } else { mForceCreation.setEnabled(false); mForceCreation.setSelection(true); } validatePage(); } } /** * {@link ModifyListener} used for live-validation of the fields content. */ private class ValidateListener extends SelectionAdapter implements ModifyListener { public void modifyText(ModifyEvent e) { validatePage(); } @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); validatePage(); } } /** * Creates the dialog. Caller should then use {@link Window#open()} and * refresh if the status is {@link Window#OK}. * * @param parentShell The parent shell. * @param avdManager The existing {@link AvdManager} to use. Must not be null. * @param imageFactory An existing {@link ImageFactory} to use. Must not be null. * @param log An existing {@link ISdkLog} where output will go. Must not be null. * @param editAvdInfo An optional {@link AvdInfo}. When null, the dialog is used * to create a new AVD. When non-null, the dialog is used to <em>edit</em> this AVD. */ protected AvdCreationDialog(Shell parentShell, AvdManager avdManager, ImageFactory imageFactory, ISdkLog log, AvdInfo editAvdInfo) { super(parentShell, 2, false); mAvdManager = avdManager; mImageFactory = imageFactory; mSdkLog = log; mEditAvdInfo = editAvdInfo; File hardwareDefs = null; SdkManager sdkMan = avdManager.getSdkManager(); if (sdkMan != null) { String sdkPath = sdkMan.getLocation(); if (sdkPath != null) { hardwareDefs = new File (sdkPath + File.separator + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI); } } if (hardwareDefs == null) { log.error(null, "Failed to load file %s from SDK", SdkConstants.FN_HARDWARE_INI); mHardwareMap = new HashMap<String, HardwareProperty>(); } else { mHardwareMap = HardwareProperties.parseHardwareDefinitions( hardwareDefs, null /*sdkLog*/); } } @Override public void create() { super.create(); Point p = getShell().getSize(); if (p.x < 400) { p.x = 400; } getShell().setSize(p); } @Override protected Control createContents(Composite parent) { Control control = super.createContents(parent); getShell().setText(mEditAvdInfo == null ? "Create new Android Virtual Device (AVD)" : "Edit Android Virtual Device (AVD)"); mOkButton = getButton(IDialogConstants.OK_ID); fillExistingAvdInfo(); validatePage(); return control; } @Override public void createDialogContent(final Composite parent) { GridData gd; GridLayout gl; Label label = new Label(parent, SWT.NONE); label.setText("Name:"); String tooltip = "Name of the new Android Virtual Device"; label.setToolTipText(tooltip); mAvdName = new Text(parent, SWT.BORDER); mAvdName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mAvdName.addModifyListener(new CreateNameModifyListener()); mAvdName.setToolTipText(tooltip); label = new Label(parent, SWT.NONE); label.setText("Target:"); tooltip = "The version of Android to use in the virtual device"; label.setToolTipText(tooltip); mTargetCombo = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN); mTargetCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mTargetCombo.setToolTipText(tooltip); mTargetCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); reloadSkinCombo(); validatePage(); } }); // --- sd card group label = new Label(parent, SWT.NONE); label.setText("SD Card:"); label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); final Group sdCardGroup = new Group(parent, SWT.NONE); sdCardGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); sdCardGroup.setLayout(new GridLayout(3, false)); mSdCardSizeRadio = new Button(sdCardGroup, SWT.RADIO); mSdCardSizeRadio.setText("Size:"); mSdCardSizeRadio.setToolTipText("Create a new SD Card file"); mSdCardSizeRadio.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { boolean sizeMode = mSdCardSizeRadio.getSelection(); enableSdCardWidgets(sizeMode); validatePage(); } }); ValidateListener validateListener = new ValidateListener(); mSdCardSize = new Text(sdCardGroup, SWT.BORDER); mSdCardSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mSdCardSize.addVerifyListener(mDigitVerifier); mSdCardSize.addModifyListener(validateListener); mSdCardSize.setToolTipText("Size of the new SD Card file (must be at least 9 MiB)"); mSdCardSizeCombo = new Combo(sdCardGroup, SWT.DROP_DOWN | SWT.READ_ONLY); mSdCardSizeCombo.add("KiB"); mSdCardSizeCombo.add("MiB"); mSdCardSizeCombo.add("GiB"); mSdCardSizeCombo.select(1); mSdCardSizeCombo.addSelectionListener(validateListener); mSdCardFileRadio = new Button(sdCardGroup, SWT.RADIO); mSdCardFileRadio.setText("File:"); mSdCardFileRadio.setToolTipText("Use an existing file for the SD Card"); mSdCardFile = new Text(sdCardGroup, SWT.BORDER); mSdCardFile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mSdCardFile.addModifyListener(validateListener); mSdCardFile.setToolTipText("File to use for the SD Card"); mBrowseSdCard = new Button(sdCardGroup, SWT.PUSH); mBrowseSdCard.setText("Browse..."); mBrowseSdCard.setToolTipText("Select the file to use for the SD Card"); mBrowseSdCard.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { onBrowseSdCard(); validatePage(); } }); mSdCardSizeRadio.setSelection(true); enableSdCardWidgets(true); // --- snapshot group label = new Label(parent, SWT.NONE); label.setText("Snapshot:"); label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); final Group snapshotGroup = new Group(parent, SWT.NONE); snapshotGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); snapshotGroup.setLayout(new GridLayout(3, false)); mSnapshotCheck = new Button(snapshotGroup, SWT.CHECK); mSnapshotCheck.setText("Enabled"); mSnapshotCheck.setToolTipText( "Emulator's state will be persisted between emulator executions"); // --- skin group label = new Label(parent, SWT.NONE); label.setText("Skin:"); label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); final Group skinGroup = new Group(parent, SWT.NONE); skinGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); skinGroup.setLayout(new GridLayout(4, false)); mSkinListRadio = new Button(skinGroup, SWT.RADIO); mSkinListRadio.setText("Built-in:"); mSkinListRadio.setToolTipText("Select an emulated screen size provided by the current Android target"); mSkinListRadio.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { boolean listMode = mSkinListRadio.getSelection(); enableSkinWidgets(listMode); validatePage(); } }); mSkinCombo = new Combo(skinGroup, SWT.READ_ONLY | SWT.DROP_DOWN); mSkinCombo.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); gd.horizontalSpan = 3; mSkinCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { // get the skin info loadSkin(); } }); mSkinSizeRadio = new Button(skinGroup, SWT.RADIO); mSkinSizeRadio.setText("Resolution:"); mSkinSizeRadio.setToolTipText("Select a custom emulated screen size"); mSkinSizeWidth = new Text(skinGroup, SWT.BORDER); mSkinSizeWidth.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mSkinSizeWidth.addVerifyListener(mDigitVerifier); mSkinSizeWidth.addModifyListener(validateListener); mSkinSizeWidth.setToolTipText("Width in pixels of the emulated screen size"); new Label(skinGroup, SWT.NONE).setText("x"); mSkinSizeHeight = new Text(skinGroup, SWT.BORDER); mSkinSizeHeight.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mSkinSizeHeight.addVerifyListener(mDigitVerifier); mSkinSizeHeight.addModifyListener(validateListener); mSkinSizeHeight.setToolTipText("Height in pixels of the emulated screen size"); mSkinListRadio.setSelection(true); enableSkinWidgets(true); // --- hardware group label = new Label(parent, SWT.NONE); label.setText("Hardware:"); label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); final Group hwGroup = new Group(parent, SWT.NONE); hwGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); hwGroup.setLayout(new GridLayout(2, false)); createHardwareTable(hwGroup); // composite for the side buttons Composite hwButtons = new Composite(hwGroup, SWT.NONE); hwButtons.setLayoutData(new GridData(GridData.FILL_VERTICAL)); hwButtons.setLayout(gl = new GridLayout(1, false)); gl.marginHeight = gl.marginWidth = 0; Button b = new Button(hwButtons, SWT.PUSH | SWT.FLAT); b.setText("New..."); b.setToolTipText("Add a new hardware property"); b.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); b.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { HardwarePropertyChooser dialog = new HardwarePropertyChooser(parent.getShell(), mHardwareMap, mProperties.keySet()); if (dialog.open() == Window.OK) { HardwareProperty choice = dialog.getProperty(); if (choice != null) { mProperties.put(choice.getName(), choice.getDefault()); mHardwareViewer.refresh(); } } } }); mDeleteHardwareProp = new Button(hwButtons, SWT.PUSH | SWT.FLAT); mDeleteHardwareProp.setText("Delete"); mDeleteHardwareProp.setToolTipText("Delete the selected hardware property"); mDeleteHardwareProp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mDeleteHardwareProp.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { ISelection selection = mHardwareViewer.getSelection(); if (selection instanceof IStructuredSelection) { String hwName = (String)((IStructuredSelection)selection).getFirstElement(); mProperties.remove(hwName); mHardwareViewer.refresh(); } } }); mDeleteHardwareProp.setEnabled(false); // --- end hardware group mForceCreation = new Button(parent, SWT.CHECK); mForceCreation.setText("Override the existing AVD with the same name"); mForceCreation.setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD."); mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, true, false, 2, 1)); mForceCreation.setEnabled(false); mForceCreation.addSelectionListener(validateListener); // add a separator to separate from the ok/cancel button label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1)); // add stuff for the error display mStatusComposite = new Composite(parent, SWT.NONE); mStatusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1)); mStatusComposite.setLayout(gl = new GridLayout(2, false)); gl.marginHeight = gl.marginWidth = 0; mStatusIcon = new Label(mStatusComposite, SWT.NONE); mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); mStatusLabel = new Label(mStatusComposite, SWT.NONE); mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mStatusLabel.setText(" \n "); //$NON-NLS-1$ reloadTargetCombo(); } /** * Creates the UI for the hardware properties table. * This creates the {@link Table}, and several viewers ({@link TableViewer}, * {@link TableViewerColumn}) and adds edit support for the 2nd column */ private void createHardwareTable(Composite parent) { final Table hardwareTable = new Table(parent, SWT.SINGLE | SWT.FULL_SELECTION); GridData gd = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); gd.widthHint = 200; gd.heightHint = 100; hardwareTable.setLayoutData(gd); hardwareTable.setHeaderVisible(true); hardwareTable.setLinesVisible(true); // -- Table viewer mHardwareViewer = new TableViewer(hardwareTable); mHardwareViewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { // it's a single selection mode, we can just access the selection index // from the table directly. mDeleteHardwareProp.setEnabled(hardwareTable.getSelectionIndex() != -1); } }); // only a content provider. Use viewers per column below (for editing support) mHardwareViewer.setContentProvider(new IStructuredContentProvider() { public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // we can just ignore this. we just use mProperties directly. } public Object[] getElements(Object arg0) { return mProperties.keySet().toArray(); } public void dispose() { // pass } }); // -- column 1: prop abstract name TableColumn col1 = new TableColumn(hardwareTable, SWT.LEFT); col1.setText("Property"); col1.setWidth(150); TableViewerColumn tvc1 = new TableViewerColumn(mHardwareViewer, col1); tvc1.setLabelProvider(new CellLabelProvider() { @Override public void update(ViewerCell cell) { String name = cell.getElement().toString(); HardwareProperty prop = mHardwareMap.get(name); if (prop != null) { cell.setText(prop.getAbstract()); } else { cell.setText(String.format("%1$s (Unknown)", name)); } } }); // -- column 2: prop value TableColumn col2 = new TableColumn(hardwareTable, SWT.LEFT); col2.setText("Value"); col2.setWidth(50); TableViewerColumn tvc2 = new TableViewerColumn(mHardwareViewer, col2); tvc2.setLabelProvider(new CellLabelProvider() { @Override public void update(ViewerCell cell) { String value = mProperties.get(cell.getElement()); cell.setText(value != null ? value : ""); } }); // add editing support to the 2nd column tvc2.setEditingSupport(new EditingSupport(mHardwareViewer) { @Override protected void setValue(Object element, Object value) { String hardwareName = (String)element; HardwareProperty property = mHardwareMap.get(hardwareName); switch (property.getType()) { case INTEGER: mProperties.put((String)element, (String)value); break; case DISKSIZE: if (HardwareProperties.DISKSIZE_PATTERN.matcher((String)value).matches()) { mProperties.put((String)element, (String)value); } break; case BOOLEAN: int index = (Integer)value; mProperties.put((String)element, HardwareProperties.BOOLEAN_VALUES[index]); break; } mHardwareViewer.refresh(element); } @Override protected Object getValue(Object element) { String hardwareName = (String)element; HardwareProperty property = mHardwareMap.get(hardwareName); String value = mProperties.get(hardwareName); switch (property.getType()) { case INTEGER: // intended fall-through. case DISKSIZE: return value; case BOOLEAN: return HardwareProperties.getBooleanValueIndex(value); } return null; } @Override protected CellEditor getCellEditor(Object element) { String hardwareName = (String)element; HardwareProperty property = mHardwareMap.get(hardwareName); switch (property.getType()) { // TODO: custom TextCellEditor that restrict input. case INTEGER: // intended fall-through. case DISKSIZE: return new TextCellEditor(hardwareTable); case BOOLEAN: return new ComboBoxCellEditor(hardwareTable, HardwareProperties.BOOLEAN_VALUES, SWT.READ_ONLY | SWT.DROP_DOWN); } return null; } @Override protected boolean canEdit(Object element) { String hardwareName = (String)element; HardwareProperty property = mHardwareMap.get(hardwareName); return property != null; } }); mHardwareViewer.setInput(mProperties); } // -- Start of internal part ---------- // Hide everything down-below from SWT designer //$hide>>$ /** * When editing an existing AVD info, fill the UI that has just been created with * the values from the AVD. */ public void fillExistingAvdInfo() { if (mEditAvdInfo == null) { return; } mAvdName.setText(mEditAvdInfo.getName()); IAndroidTarget target = mEditAvdInfo.getTarget(); if (target != null && !mCurrentTargets.isEmpty()) { // Try to select the target in the target combo. // This will fail if the AVD needs to be repaired. // // This is a linear search but the list is always // small enough and we only do this once. int n = mTargetCombo.getItemCount(); for (int i = 0;i < n; i++) { if (target.equals(mCurrentTargets.get(mTargetCombo.getItem(i)))) { mTargetCombo.select(i); reloadSkinCombo(); break; } } } Map<String, String> props = mEditAvdInfo.getProperties(); // First try the skin name and if it doesn't work fallback on the skin path nextSkin: for (int s = 0; s < 2; s++) { String skin = props.get(s == 0 ? AvdManager.AVD_INI_SKIN_NAME : AvdManager.AVD_INI_SKIN_PATH); if (skin != null && skin.length() > 0) { Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin); if (m.matches() && m.groupCount() == 2) { enableSkinWidgets(false); mSkinListRadio.setSelection(false); mSkinSizeRadio.setSelection(true); mSkinSizeWidth.setText(m.group(1)); mSkinSizeHeight.setText(m.group(2)); break nextSkin; } else { enableSkinWidgets(true); mSkinSizeRadio.setSelection(false); mSkinListRadio.setSelection(true); int n = mSkinCombo.getItemCount(); for (int i = 0; i < n; i++) { if (skin.equals(mSkinCombo.getItem(i))) { mSkinCombo.select(i); break nextSkin; } } } } } String sdcard = props.get(AvdManager.AVD_INI_SDCARD_PATH); if (sdcard != null && sdcard.length() > 0) { enableSdCardWidgets(false); mSdCardSizeRadio.setSelection(false); mSdCardFileRadio.setSelection(true); mSdCardFile.setText(sdcard); } sdcard = props.get(AvdManager.AVD_INI_SDCARD_SIZE); if (sdcard != null && sdcard.length() > 0) { Matcher m = AvdManager.SDCARD_SIZE_PATTERN.matcher(sdcard); if (m.matches() && m.groupCount() == 2) { enableSdCardWidgets(true); mSdCardFileRadio.setSelection(false); mSdCardSizeRadio.setSelection(true); mSdCardSize.setText(m.group(1)); String suffix = m.group(2); int n = mSdCardSizeCombo.getItemCount(); for (int i = 0; i < n; i++) { if (mSdCardSizeCombo.getItem(i).startsWith(suffix)) { mSdCardSizeCombo.select(i); } } } } String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT); if (snapshots != null && snapshots.length() > 0) { mSnapshotCheck.setSelection(snapshots.equals("true")); } mProperties.clear(); mProperties.putAll(props); // Cleanup known non-hardware properties mProperties.remove(AvdManager.AVD_INI_SKIN_PATH); mProperties.remove(AvdManager.AVD_INI_SKIN_NAME); mProperties.remove(AvdManager.AVD_INI_SDCARD_SIZE); mProperties.remove(AvdManager.AVD_INI_SDCARD_PATH); mProperties.remove(AvdManager.AVD_INI_SNAPSHOT_PRESENT); mProperties.remove(AvdManager.AVD_INI_IMAGES_1); mProperties.remove(AvdManager.AVD_INI_IMAGES_2); mHardwareViewer.refresh(); } @Override protected void okPressed() { if (createAvd()) { super.okPressed(); } } /** * Enable or disable the sd card widgets. * @param sizeMode if true the size-based widgets are to be enabled, and the file-based ones * disabled. */ private void enableSdCardWidgets(boolean sizeMode) { mSdCardSize.setEnabled(sizeMode); mSdCardSizeCombo.setEnabled(sizeMode); mSdCardFile.setEnabled(!sizeMode); mBrowseSdCard.setEnabled(!sizeMode); } /** * Enable or disable the skin widgets. * @param listMode if true the list-based widgets are to be enabled, and the size-based ones * disabled. */ private void enableSkinWidgets(boolean listMode) { mSkinCombo.setEnabled(listMode); mSkinSizeWidth.setEnabled(!listMode); mSkinSizeHeight.setEnabled(!listMode); } private void onBrowseSdCard() { FileDialog dlg = new FileDialog(getContents().getShell(), SWT.OPEN); dlg.setText("Choose SD Card image file."); String fileName = dlg.open(); if (fileName != null) { mSdCardFile.setText(fileName); } } private void reloadTargetCombo() { String selected = null; int index = mTargetCombo.getSelectionIndex(); if (index >= 0) { selected = mTargetCombo.getItem(index); } mCurrentTargets.clear(); mTargetCombo.removeAll(); boolean found = false; index = -1; SdkManager sdkManager = mAvdManager.getSdkManager(); if (sdkManager != null) { for (IAndroidTarget target : sdkManager.getTargets()) { String name; if (target.isPlatform()) { name = String.format("%s - API Level %s", target.getName(), target.getVersion().getApiString()); } else { name = String.format("%s (%s) - API Level %s", target.getName(), target.getVendor(), target.getVersion().getApiString()); } mCurrentTargets.put(name, target); mTargetCombo.add(name); if (!found) { index++; found = name.equals(selected); } } } mTargetCombo.setEnabled(mCurrentTargets.size() > 0); if (found) { mTargetCombo.select(index); } reloadSkinCombo(); } private void reloadSkinCombo() { String selected = null; int index = mSkinCombo.getSelectionIndex(); if (index >= 0) { selected = mSkinCombo.getItem(index); } mSkinCombo.removeAll(); mSkinCombo.setEnabled(false); index = mTargetCombo.getSelectionIndex(); if (index >= 0) { String targetName = mTargetCombo.getItem(index); boolean found = false; IAndroidTarget target = mCurrentTargets.get(targetName); if (target != null) { mSkinCombo.add(String.format("Default (%s)", target.getDefaultSkin())); index = -1; for (String skin : target.getSkins()) { mSkinCombo.add(skin); if (!found) { index++; found = skin.equals(selected); } } mSkinCombo.setEnabled(true); if (found) { mSkinCombo.select(index); } else { mSkinCombo.select(0); // default loadSkin(); } } } } /** * Validates the fields, displays errors and warnings. * Enables the finish button if there are no errors. */ private void validatePage() { String error = null; String warning = null; // Validate AVD name String avdName = mAvdName.getText().trim(); boolean hasAvdName = avdName.length() > 0; boolean isCreate = mEditAvdInfo == null || !avdName.equals(mEditAvdInfo.getName()); if (hasAvdName && !AvdManager.RE_AVD_NAME.matcher(avdName).matches()) { error = String.format( "AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s", avdName, AvdManager.CHARS_AVD_NAME); } // Validate target if (hasAvdName && error == null && mTargetCombo.getSelectionIndex() < 0) { error = "A target must be selected in order to create an AVD."; } // Validate SDCard path or value if (error == null) { // get the mode. We only need to check the file since the // verifier on the size Text will prevent invalid input boolean sdcardFileMode = mSdCardFileRadio.getSelection(); if (sdcardFileMode) { String sdName = mSdCardFile.getText().trim(); if (sdName.length() > 0 && !new File(sdName).isFile()) { error = "SD Card path isn't valid."; } } else { String valueString = mSdCardSize.getText(); if (valueString.length() > 0) { long value = 0; try { value = Long.parseLong(valueString); int sizeIndex = mSdCardSizeCombo.getSelectionIndex(); if (sizeIndex >= 0) { // index 0 shifts by 10 (1024=K), index 1 by 20, etc. value <<= 10*(1 + sizeIndex); } if (value < AvdManager.SDCARD_MIN_BYTE_SIZE || value > AvdManager.SDCARD_MAX_BYTE_SIZE) { value = 0; } } catch (Exception e) { // ignore, we'll test value below. } if (value <= 0) { error = "SD Card size is invalid. Range is 9 MiB..1023 GiB."; } } } } // validate the skin if (error == null) { // get the mode, we should only check if it's in size mode since // the built-in list mode is always valid. if (mSkinSizeRadio.getSelection()) { // need both with and heigh to be non null String width = mSkinSizeWidth.getText(); // no need for trim, since the verifier String height = mSkinSizeHeight.getText(); // rejects non digit. if (width.length() == 0 || height.length() == 0) { error = "Skin size is incorrect.\nBoth dimensions must be > 0."; } } } // Check for duplicate AVD name if (isCreate && hasAvdName && error == null && !mForceCreation.getSelection()) { Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(avdName); assert conflict != null; switch(conflict.getFirst()) { case NO_CONFLICT: break; case CONFLICT_EXISTING_AVD: case CONFLICT_INVALID_AVD: error = String.format( "The AVD name '%s' is already used.\n" + "Check \"Override the existing AVD\" to delete the existing one.", avdName); break; case CONFLICT_EXISTING_PATH: error = String.format( "Conflict with %s\n" + "Check \"Override the existing AVD\" to delete the existing one.", conflict.getSecond()); break; default: // Hmm not supposed to happen... probably someone expanded the // enum without adding something here. In this case just do an // assert and use a generic error message. error = String.format( "Conflict %s with %s.\n" + "Check \"Override the existing AVD\" to delete the existing one.", conflict.getFirst().toString(), conflict.getSecond()); assert false; break; } } if (error == null && mEditAvdInfo != null && isCreate) { warning = String.format("The AVD '%1$s' will be duplicated into '%2$s'.", mEditAvdInfo.getName(), avdName); } // Validate the create button boolean can_create = hasAvdName && error == null; if (can_create) { can_create &= mTargetCombo.getSelectionIndex() >= 0; } mOkButton.setEnabled(can_create); // Adjust the create button label as needed if (isCreate) { mOkButton.setText("Create AVD"); } else { mOkButton.setText("Edit AVD"); } // -- update UI if (error != null) { mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png")); //$NON-NLS-1$ mStatusLabel.setText(error); } else if (warning != null) { mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png")); //$NON-NLS-1$ mStatusLabel.setText(warning); } else { mStatusIcon.setImage(null); mStatusLabel.setText(" \n "); //$NON-NLS-1$ } mStatusComposite.pack(true); } private void loadSkin() { int targetIndex = mTargetCombo.getSelectionIndex(); if (targetIndex < 0) { return; } // resolve the target. String targetName = mTargetCombo.getItem(targetIndex); IAndroidTarget target = mCurrentTargets.get(targetName); if (target == null) { return; } // get the skin name String skinName = null; int skinIndex = mSkinCombo.getSelectionIndex(); if (skinIndex < 0) { return; } else if (skinIndex == 0) { // default skin for the target skinName = target.getDefaultSkin(); } else { skinName = mSkinCombo.getItem(skinIndex); } // load the skin properties String path = target.getPath(IAndroidTarget.SKINS); File skin = new File(path, skinName); if (skin.isDirectory() == false && target.isPlatform() == false) { // it's possible the skin is in the parent target path = target.getParent().getPath(IAndroidTarget.SKINS); skin = new File(path, skinName); } if (skin.isDirectory() == false) { return; } // now get the hardware.ini from the add-on (if applicable) and from the skin // (if applicable) HashMap<String, String> hardwareValues = new HashMap<String, String>(); if (target.isPlatform() == false) { FileWrapper targetHardwareFile = new FileWrapper(target.getLocation(), AvdManager.HARDWARE_INI); if (targetHardwareFile.isFile()) { Map<String, String> targetHardwareConfig = ProjectProperties.parsePropertyFile( targetHardwareFile, null /*log*/); if (targetHardwareConfig != null) { hardwareValues.putAll(targetHardwareConfig); } } } // from the skin FileWrapper skinHardwareFile = new FileWrapper(skin, AvdManager.HARDWARE_INI); if (skinHardwareFile.isFile()) { Map<String, String> skinHardwareConfig = ProjectProperties.parsePropertyFile( skinHardwareFile, null /*log*/); if (skinHardwareConfig != null) { hardwareValues.putAll(skinHardwareConfig); } } // now set those values in the list of properties for the AVD. // We just check that none of those properties have been edited by the user yet. for (Entry<String, String> entry : hardwareValues.entrySet()) { if (mEditedProperties.contains(entry.getKey()) == false) { mProperties.put(entry.getKey(), entry.getValue()); } } mHardwareViewer.refresh(); } /** * Creates an AVD from the values in the UI. Called when the user presses the OK button. */ private boolean createAvd() { String avdName = mAvdName.getText().trim(); int targetIndex = mTargetCombo.getSelectionIndex(); // quick check on the name and the target selection if (avdName.length() == 0 || targetIndex < 0) { return false; } // resolve the target. String targetName = mTargetCombo.getItem(targetIndex); IAndroidTarget target = mCurrentTargets.get(targetName); if (target == null) { return false; } // get the SD card data from the UI. String sdName = null; if (mSdCardSizeRadio.getSelection()) { // size mode String value = mSdCardSize.getText().trim(); if (value.length() > 0) { sdName = value; // add the unit switch (mSdCardSizeCombo.getSelectionIndex()) { case 0: sdName += "K"; //$NON-NLS-1$ break; case 1: sdName += "M"; //$NON-NLS-1$ break; case 2: sdName += "G"; //$NON-NLS-1$ break; default: // shouldn't be here assert false; } } } else { // file mode. sdName = mSdCardFile.getText().trim(); } // get the Skin data from the UI String skinName = null; if (mSkinListRadio.getSelection()) { // built-in list provides the skin int skinIndex = mSkinCombo.getSelectionIndex(); if (skinIndex > 0) { // index 0 is the default, we don't use it skinName = mSkinCombo.getItem(skinIndex); } } else { // size mode. get both size and writes it as a skin name // thanks to validatePage() we know the content of the fields is correct skinName = mSkinSizeWidth.getText() + "x" + mSkinSizeHeight.getText(); //$NON-NLS-1$ } 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 creating AVD '%s':", avdName), getContents().getDisplay(), false /*logErrorsOnly*/); } File avdFolder = null; try { avdFolder = AvdManager.AvdInfo.getAvdFolder(avdName); } catch (AndroidLocationException e) { return false; } boolean force = mForceCreation.getSelection(); boolean snapshot = mSnapshotCheck.getSelection(); boolean success = false; AvdInfo avdInfo = mAvdManager.createAvd( avdFolder, avdName, target, skinName, sdName, mProperties, force, snapshot, log); success = avdInfo != null; if (log instanceof MessageBoxLog) { ((MessageBoxLog) log).displayResult(success); } return success; } // End of hiding from SWT Designer //$hide<<$ }