1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.sdkuilib.internal.widgets;
18
19import com.android.SdkConstants;
20import com.android.io.FileWrapper;
21import com.android.prefs.AndroidLocation.AndroidLocationException;
22import com.android.sdklib.IAndroidTarget;
23import com.android.sdklib.ISystemImage;
24import com.android.sdklib.SdkManager;
25import com.android.sdklib.internal.avd.AvdInfo;
26import com.android.sdklib.internal.avd.AvdManager;
27import com.android.sdklib.internal.avd.AvdManager.AvdConflict;
28import com.android.sdklib.internal.avd.HardwareProperties;
29import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
30import com.android.sdklib.internal.project.ProjectProperties;
31import com.android.sdkuilib.internal.repository.icons.ImageFactory;
32import com.android.sdkuilib.ui.GridDialog;
33import com.android.utils.ILogger;
34import com.android.utils.Pair;
35
36import org.eclipse.jface.dialogs.IDialogConstants;
37import org.eclipse.jface.viewers.CellEditor;
38import org.eclipse.jface.viewers.CellLabelProvider;
39import org.eclipse.jface.viewers.ComboBoxCellEditor;
40import org.eclipse.jface.viewers.EditingSupport;
41import org.eclipse.jface.viewers.ISelection;
42import org.eclipse.jface.viewers.ISelectionChangedListener;
43import org.eclipse.jface.viewers.IStructuredContentProvider;
44import org.eclipse.jface.viewers.IStructuredSelection;
45import org.eclipse.jface.viewers.SelectionChangedEvent;
46import org.eclipse.jface.viewers.TableViewer;
47import org.eclipse.jface.viewers.TableViewerColumn;
48import org.eclipse.jface.viewers.TextCellEditor;
49import org.eclipse.jface.viewers.Viewer;
50import org.eclipse.jface.viewers.ViewerCell;
51import org.eclipse.jface.window.Window;
52import org.eclipse.swt.SWT;
53import org.eclipse.swt.events.ModifyEvent;
54import org.eclipse.swt.events.ModifyListener;
55import org.eclipse.swt.events.SelectionAdapter;
56import org.eclipse.swt.events.SelectionEvent;
57import org.eclipse.swt.events.VerifyEvent;
58import org.eclipse.swt.events.VerifyListener;
59import org.eclipse.swt.graphics.Point;
60import org.eclipse.swt.layout.GridData;
61import org.eclipse.swt.layout.GridLayout;
62import org.eclipse.swt.widgets.Button;
63import org.eclipse.swt.widgets.Combo;
64import org.eclipse.swt.widgets.Composite;
65import org.eclipse.swt.widgets.Control;
66import org.eclipse.swt.widgets.FileDialog;
67import org.eclipse.swt.widgets.Group;
68import org.eclipse.swt.widgets.Label;
69import org.eclipse.swt.widgets.Shell;
70import org.eclipse.swt.widgets.Table;
71import org.eclipse.swt.widgets.TableColumn;
72import org.eclipse.swt.widgets.Text;
73
74import java.io.File;
75import java.util.ArrayList;
76import java.util.HashMap;
77import java.util.Map;
78import java.util.Map.Entry;
79import java.util.TreeMap;
80import java.util.regex.Matcher;
81
82/**
83 * AVD creation or edit dialog.
84 *
85 * TODO:
86 * - use SdkTargetSelector instead of Combo
87 * - tooltips on widgets.
88 *
89 */
90final class LegacyAvdEditDialog extends GridDialog {
91
92    private final AvdManager mAvdManager;
93    private final TreeMap<String, IAndroidTarget> mCurrentTargets =
94        new TreeMap<String, IAndroidTarget>();
95
96    private final Map<String, HardwareProperty> mHardwareMap;
97    private final Map<String, String> mProperties = new HashMap<String, String>();
98    // a list of user-edited properties.
99    private final ArrayList<String> mEditedProperties = new ArrayList<String>();
100    private final ImageFactory mImageFactory;
101    private final ILogger mSdkLog;
102    /**
103     * The original AvdInfo if we're editing an existing AVD.
104     * Null when we're creating a new AVD.
105     */
106    private final AvdInfo mEditAvdInfo;
107
108    private Text mAvdName;
109    private Combo mTargetCombo;
110
111    private Combo mAbiTypeCombo;
112    private String mAbiType;
113
114    private Button mSdCardSizeRadio;
115    private Text mSdCardSize;
116    private Combo mSdCardSizeCombo;
117
118    private Text mSdCardFile;
119    private Button mBrowseSdCard;
120    private Button mSdCardFileRadio;
121
122    private Button mSnapshotCheck;
123
124    private Button mSkinListRadio;
125    private Combo mSkinCombo;
126
127    private Button mSkinSizeRadio;
128    private Text mSkinSizeWidth;
129    private Text mSkinSizeHeight;
130
131    private TableViewer mHardwareViewer;
132    private Button mDeleteHardwareProp;
133
134    private Button mForceCreation;
135    private Button mOkButton;
136    private Label mStatusIcon;
137    private Label mStatusLabel;
138    private Composite mStatusComposite;
139
140    /**
141     * {@link VerifyListener} for {@link Text} widgets that should only contains numbers.
142     */
143    private final VerifyListener mDigitVerifier = new VerifyListener() {
144        @Override
145        public void verifyText(VerifyEvent event) {
146            int count = event.text.length();
147            for (int i = 0 ; i < count ; i++) {
148                char c = event.text.charAt(i);
149                if (c < '0' || c > '9') {
150                    event.doit = false;
151                    return;
152                }
153            }
154        }
155    };
156
157    /**
158     * Callback when the AVD name is changed.
159     * When creating a new AVD, enables the force checkbox if the name is a duplicate.
160     * When editing an existing AVD, it's OK for the name to match the existing AVD.
161     */
162    private class CreateNameModifyListener implements ModifyListener {
163        @Override
164        public void modifyText(ModifyEvent e) {
165            String name = mAvdName.getText().trim();
166            if (mEditAvdInfo == null || !name.equals(mEditAvdInfo.getName())) {
167                // Case where we're creating a new AVD or editing an existing one
168                // and the AVD name has been changed... check for name uniqueness.
169
170                Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(name);
171                if (conflict.getFirst() != AvdManager.AvdConflict.NO_CONFLICT) {
172                    // If we're changing the state from disabled to enabled, make sure
173                    // to uncheck the button, to force the user to voluntarily re-enforce it.
174                    // This happens when editing an existing AVD and changing the name from
175                    // the existing AVD to another different existing AVD.
176                    if (!mForceCreation.isEnabled()) {
177                        mForceCreation.setEnabled(true);
178                        mForceCreation.setSelection(false);
179                    }
180                } else {
181                    mForceCreation.setEnabled(false);
182                    mForceCreation.setSelection(false);
183                }
184            } else {
185                // Case where we're editing an existing AVD with the name unchanged.
186
187                mForceCreation.setEnabled(false);
188                mForceCreation.setSelection(false);
189            }
190            validatePage();
191        }
192    }
193
194    /**
195     * {@link ModifyListener} used for live-validation of the fields content.
196     */
197    private class ValidateListener extends SelectionAdapter implements ModifyListener {
198        @Override
199        public void modifyText(ModifyEvent e) {
200            validatePage();
201        }
202
203        @Override
204        public void widgetSelected(SelectionEvent e) {
205            super.widgetSelected(e);
206            validatePage();
207        }
208    }
209
210    /**
211     * Creates the dialog. Caller should then use {@link Window#open()} and
212     * refresh if the status is {@link Window#OK}.
213     *
214     * @param parentShell The parent shell.
215     * @param avdManager The existing {@link AvdManager} to use. Must not be null.
216     * @param imageFactory An existing {@link ImageFactory} to use. Must not be null.
217     * @param log An existing {@link ILogger} where output will go. Must not be null.
218     * @param editAvdInfo An optional {@link AvdInfo}. When null, the dialog is used
219     *   to create a new AVD. When non-null, the dialog is used to <em>edit</em> this AVD.
220     */
221    protected LegacyAvdEditDialog(Shell parentShell,
222            AvdManager avdManager,
223            ImageFactory imageFactory,
224            ILogger log,
225            AvdInfo editAvdInfo) {
226        super(parentShell, 2, false);
227        mAvdManager = avdManager;
228        mImageFactory = imageFactory;
229        mSdkLog = log;
230        mEditAvdInfo = editAvdInfo;
231
232        File hardwareDefs = null;
233
234        SdkManager sdkMan = avdManager.getSdkManager();
235        if (sdkMan != null) {
236            String sdkPath = sdkMan.getLocation();
237            if (sdkPath != null) {
238                hardwareDefs = new File (sdkPath + File.separator +
239                        SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
240            }
241        }
242
243        if (hardwareDefs == null) {
244            log.error(null, "Failed to load file %s from SDK", SdkConstants.FN_HARDWARE_INI);
245            mHardwareMap = new HashMap<String, HardwareProperty>();
246        } else {
247            mHardwareMap = HardwareProperties.parseHardwareDefinitions(
248                hardwareDefs, null /*sdkLog*/);
249        }
250    }
251
252    @Override
253    public void create() {
254        super.create();
255
256        Point p = getShell().getSize();
257        if (p.x < 400) {
258            p.x = 400;
259        }
260        getShell().setSize(p);
261    }
262
263    @Override
264    protected Control createContents(Composite parent) {
265        Control control = super.createContents(parent);
266        getShell().setText(mEditAvdInfo == null ? "Create new Android Virtual Device (AVD)"
267                                                : "Edit Android Virtual Device (AVD)");
268
269        mOkButton = getButton(IDialogConstants.OK_ID);
270
271        fillExistingAvdInfo();
272        validatePage();
273
274        return control;
275    }
276
277    @Override
278    public void createDialogContent(final Composite parent) {
279        GridData gd;
280        GridLayout gl;
281
282        Label label = new Label(parent, SWT.NONE);
283        label.setText("Name:");
284        String tooltip = "Name of the new Android Virtual Device";
285        label.setToolTipText(tooltip);
286
287        mAvdName = new Text(parent, SWT.BORDER);
288        mAvdName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
289        mAvdName.addModifyListener(new CreateNameModifyListener());
290        mAvdName.setToolTipText(tooltip);
291
292        label = new Label(parent, SWT.NONE);
293        label.setText("Target:");
294        tooltip = "The version of Android to use in the virtual device";
295        label.setToolTipText(tooltip);
296
297        mTargetCombo = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
298        mTargetCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
299        mTargetCombo.setToolTipText(tooltip);
300        mTargetCombo.addSelectionListener(new SelectionAdapter() {
301            @Override
302            public void widgetSelected(SelectionEvent e) {
303                super.widgetSelected(e);
304                reloadSkinCombo();
305                reloadAbiTypeCombo();
306                validatePage();
307            }
308        });
309
310        //ABI group
311        label = new Label(parent, SWT.NONE);
312        label.setText("CPU/ABI:");
313        tooltip = "The CPU/ABI to use in the virtual device";
314        label.setToolTipText(tooltip);
315
316         mAbiTypeCombo = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
317         mAbiTypeCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
318         mAbiTypeCombo.setToolTipText(tooltip);
319         mAbiTypeCombo.addSelectionListener(new SelectionAdapter() {
320         @Override
321         public void widgetSelected(SelectionEvent e) {
322                     super.widgetSelected(e);
323                     validatePage();
324                 }
325         });
326         mAbiTypeCombo.setEnabled(false);
327
328        // --- sd card group
329        label = new Label(parent, SWT.NONE);
330        label.setText("SD Card:");
331        label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
332                false, false));
333
334        final Group sdCardGroup = new Group(parent, SWT.NONE);
335        sdCardGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
336        sdCardGroup.setLayout(new GridLayout(3, false));
337
338        mSdCardSizeRadio = new Button(sdCardGroup, SWT.RADIO);
339        mSdCardSizeRadio.setText("Size:");
340        mSdCardSizeRadio.setToolTipText("Create a new SD Card file");
341        mSdCardSizeRadio.addSelectionListener(new SelectionAdapter() {
342            @Override
343            public void widgetSelected(SelectionEvent arg0) {
344                boolean sizeMode = mSdCardSizeRadio.getSelection();
345                enableSdCardWidgets(sizeMode);
346                validatePage();
347            }
348        });
349
350        ValidateListener validateListener = new ValidateListener();
351
352        mSdCardSize = new Text(sdCardGroup, SWT.BORDER);
353        mSdCardSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
354        mSdCardSize.addVerifyListener(mDigitVerifier);
355        mSdCardSize.addModifyListener(validateListener);
356        mSdCardSize.setToolTipText("Size of the new SD Card file (must be at least 9 MiB)");
357
358        mSdCardSizeCombo = new Combo(sdCardGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
359        mSdCardSizeCombo.add("KiB");
360        mSdCardSizeCombo.add("MiB");
361        mSdCardSizeCombo.add("GiB");
362        mSdCardSizeCombo.select(1);
363        mSdCardSizeCombo.addSelectionListener(validateListener);
364
365        mSdCardFileRadio = new Button(sdCardGroup, SWT.RADIO);
366        mSdCardFileRadio.setText("File:");
367        mSdCardFileRadio.setToolTipText("Use an existing file for the SD Card");
368
369        mSdCardFile = new Text(sdCardGroup, SWT.BORDER);
370        mSdCardFile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
371        mSdCardFile.addModifyListener(validateListener);
372        mSdCardFile.setToolTipText("File to use for the SD Card");
373
374        mBrowseSdCard = new Button(sdCardGroup, SWT.PUSH);
375        mBrowseSdCard.setText("Browse...");
376        mBrowseSdCard.setToolTipText("Select the file to use for the SD Card");
377        mBrowseSdCard.addSelectionListener(new SelectionAdapter() {
378           @Override
379            public void widgetSelected(SelectionEvent arg0) {
380               onBrowseSdCard();
381               validatePage();
382            }
383        });
384
385        mSdCardSizeRadio.setSelection(true);
386        enableSdCardWidgets(true);
387
388        // --- snapshot group
389
390        label = new Label(parent, SWT.NONE);
391        label.setText("Snapshot:");
392        label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
393            false, false));
394
395        final Group snapshotGroup = new Group(parent, SWT.NONE);
396        snapshotGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
397        snapshotGroup.setLayout(new GridLayout(3, false));
398
399        mSnapshotCheck = new Button(snapshotGroup, SWT.CHECK);
400        mSnapshotCheck.setText("Enabled");
401        mSnapshotCheck.setToolTipText(
402                "Emulator's state will be persisted between emulator executions");
403
404        // --- skin group
405        label = new Label(parent, SWT.NONE);
406        label.setText("Skin:");
407        label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
408                false, false));
409
410        final Group skinGroup = new Group(parent, SWT.NONE);
411        skinGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
412        skinGroup.setLayout(new GridLayout(4, false));
413
414        mSkinListRadio = new Button(skinGroup, SWT.RADIO);
415        mSkinListRadio.setText("Built-in:");
416        mSkinListRadio.setToolTipText("Select an emulated screen size provided by the current Android target");
417        mSkinListRadio.addSelectionListener(new SelectionAdapter() {
418            @Override
419            public void widgetSelected(SelectionEvent arg0) {
420                boolean listMode = mSkinListRadio.getSelection();
421                enableSkinWidgets(listMode);
422                validatePage();
423            }
424        });
425
426        mSkinCombo = new Combo(skinGroup, SWT.READ_ONLY | SWT.DROP_DOWN);
427        mSkinCombo.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
428        gd.horizontalSpan = 3;
429        mSkinCombo.addSelectionListener(new SelectionAdapter() {
430            @Override
431            public void widgetSelected(SelectionEvent arg0) {
432                // get the skin info
433                loadSkin();
434            }
435        });
436
437        mSkinSizeRadio = new Button(skinGroup, SWT.RADIO);
438        mSkinSizeRadio.setText("Resolution:");
439        mSkinSizeRadio.setToolTipText("Select a custom emulated screen size");
440
441        mSkinSizeWidth = new Text(skinGroup, SWT.BORDER);
442        mSkinSizeWidth.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
443        mSkinSizeWidth.addVerifyListener(mDigitVerifier);
444        mSkinSizeWidth.addModifyListener(validateListener);
445        mSkinSizeWidth.setToolTipText("Width in pixels of the emulated screen size");
446
447        new Label(skinGroup, SWT.NONE).setText("x");
448
449        mSkinSizeHeight = new Text(skinGroup, SWT.BORDER);
450        mSkinSizeHeight.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
451        mSkinSizeHeight.addVerifyListener(mDigitVerifier);
452        mSkinSizeHeight.addModifyListener(validateListener);
453        mSkinSizeHeight.setToolTipText("Height in pixels of the emulated screen size");
454
455        mSkinListRadio.setSelection(true);
456        enableSkinWidgets(true);
457
458        // --- hardware group
459        label = new Label(parent, SWT.NONE);
460        label.setText("Hardware:");
461        label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
462                false, false));
463
464        final Group hwGroup = new Group(parent, SWT.NONE);
465        hwGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
466        hwGroup.setLayout(new GridLayout(2, false));
467
468        createHardwareTable(hwGroup);
469
470        // composite for the side buttons
471        Composite hwButtons = new Composite(hwGroup, SWT.NONE);
472        hwButtons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
473        hwButtons.setLayout(gl = new GridLayout(1, false));
474        gl.marginHeight = gl.marginWidth = 0;
475
476        Button b = new Button(hwButtons, SWT.PUSH | SWT.FLAT);
477        b.setText("New...");
478        b.setToolTipText("Add a new hardware property");
479        b.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
480        b.addSelectionListener(new SelectionAdapter() {
481            @Override
482            public void widgetSelected(SelectionEvent event) {
483                HardwarePropertyChooser dialog = new HardwarePropertyChooser(parent.getShell(),
484                        mHardwareMap, mProperties.keySet());
485                if (dialog.open() == Window.OK) {
486                    HardwareProperty choice = dialog.getProperty();
487                    if (choice != null) {
488                        mProperties.put(choice.getName(), choice.getDefault());
489                        mHardwareViewer.refresh();
490                    }
491                }
492            }
493        });
494        mDeleteHardwareProp = new Button(hwButtons, SWT.PUSH | SWT.FLAT);
495        mDeleteHardwareProp.setText("Delete");
496        mDeleteHardwareProp.setToolTipText("Delete the selected hardware property");
497        mDeleteHardwareProp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
498        mDeleteHardwareProp.addSelectionListener(new SelectionAdapter() {
499            @Override
500            public void widgetSelected(SelectionEvent arg0) {
501                ISelection selection = mHardwareViewer.getSelection();
502                if (selection instanceof IStructuredSelection) {
503                    String hwName = (String)((IStructuredSelection)selection).getFirstElement();
504                    mProperties.remove(hwName);
505                    mHardwareViewer.refresh();
506                }
507            }
508        });
509        mDeleteHardwareProp.setEnabled(false);
510
511        // --- end hardware group
512
513        mForceCreation = new Button(parent, SWT.CHECK);
514        mForceCreation.setText("Override the existing AVD with the same name");
515        mForceCreation.setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD.");
516        mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER,
517                true, false, 2, 1));
518        mForceCreation.setEnabled(false);
519        mForceCreation.addSelectionListener(validateListener);
520
521        // add a separator to separate from the ok/cancel button
522        label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
523        label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
524
525        // add stuff for the error display
526        mStatusComposite = new Composite(parent, SWT.NONE);
527        mStatusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
528                true, false, 3, 1));
529        mStatusComposite.setLayout(gl = new GridLayout(2, false));
530        gl.marginHeight = gl.marginWidth = 0;
531
532        mStatusIcon = new Label(mStatusComposite, SWT.NONE);
533        mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
534                false, false));
535        mStatusLabel = new Label(mStatusComposite, SWT.NONE);
536        mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
537        mStatusLabel.setText(" \n "); //$NON-NLS-1$
538
539        reloadTargetCombo();
540    }
541
542    /**
543     * Creates the UI for the hardware properties table.
544     * This creates the {@link Table}, and several viewers ({@link TableViewer},
545     * {@link TableViewerColumn}) and adds edit support for the 2nd column
546     */
547    private void createHardwareTable(Composite parent) {
548        final Table hardwareTable = new Table(parent, SWT.SINGLE | SWT.FULL_SELECTION);
549        GridData gd = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
550        gd.widthHint = 200;
551        gd.heightHint = 100;
552        hardwareTable.setLayoutData(gd);
553        hardwareTable.setHeaderVisible(true);
554        hardwareTable.setLinesVisible(true);
555
556        // -- Table viewer
557        mHardwareViewer = new TableViewer(hardwareTable);
558        mHardwareViewer.addSelectionChangedListener(new ISelectionChangedListener() {
559            @Override
560            public void selectionChanged(SelectionChangedEvent event) {
561                // it's a single selection mode, we can just access the selection index
562                // from the table directly.
563                mDeleteHardwareProp.setEnabled(hardwareTable.getSelectionIndex() != -1);
564            }
565        });
566
567        // only a content provider. Use viewers per column below (for editing support)
568        mHardwareViewer.setContentProvider(new IStructuredContentProvider() {
569            @Override
570            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
571                // we can just ignore this. we just use mProperties directly.
572            }
573
574            @Override
575            public Object[] getElements(Object arg0) {
576                return mProperties.keySet().toArray();
577            }
578
579            @Override
580            public void dispose() {
581                // pass
582            }
583        });
584
585        // -- column 1: prop abstract name
586        TableColumn col1 = new TableColumn(hardwareTable, SWT.LEFT);
587        col1.setText("Property");
588        col1.setWidth(150);
589        TableViewerColumn tvc1 = new TableViewerColumn(mHardwareViewer, col1);
590        tvc1.setLabelProvider(new CellLabelProvider() {
591            @Override
592            public void update(ViewerCell cell) {
593                String name = cell.getElement().toString();
594                HardwareProperty prop = mHardwareMap.get(name);
595                if (prop != null) {
596                    cell.setText(prop.getAbstract());
597                } else {
598                    cell.setText(String.format("%1$s (Unknown)", name));
599                }
600            }
601        });
602
603        // -- column 2: prop value
604        TableColumn col2 = new TableColumn(hardwareTable, SWT.LEFT);
605        col2.setText("Value");
606        col2.setWidth(50);
607        TableViewerColumn tvc2 = new TableViewerColumn(mHardwareViewer, col2);
608        tvc2.setLabelProvider(new CellLabelProvider() {
609            @Override
610            public void update(ViewerCell cell) {
611                String value = mProperties.get(cell.getElement());
612                cell.setText(value != null ? value : "");
613            }
614        });
615
616        // add editing support to the 2nd column
617        tvc2.setEditingSupport(new EditingSupport(mHardwareViewer) {
618            @Override
619            protected void setValue(Object element, Object value) {
620                String hardwareName = (String)element;
621                HardwareProperty property = mHardwareMap.get(hardwareName);
622                int index;
623                switch (property.getType()) {
624                    case INTEGER:
625                        mProperties.put((String)element, (String)value);
626                        break;
627                    case DISKSIZE:
628                        if (HardwareProperties.DISKSIZE_PATTERN.matcher((String)value).matches()) {
629                            mProperties.put((String)element, (String)value);
630                        }
631                        break;
632                    case BOOLEAN:
633                        index = (Integer)value;
634                        mProperties.put((String)element, HardwareProperties.BOOLEAN_VALUES[index]);
635                        break;
636                    case STRING_ENUM:
637                    case INTEGER_ENUM:
638                        // For a combo, value is the index of the enum to use.
639                        index = (Integer)value;
640                        String[] values = property.getEnum();
641                        if (values != null && values.length > index) {
642                            mProperties.put((String)element, values[index]);
643                        }
644                        break;
645                }
646                mHardwareViewer.refresh(element);
647            }
648
649            @Override
650            protected Object getValue(Object element) {
651                String hardwareName = (String)element;
652                HardwareProperty property = mHardwareMap.get(hardwareName);
653                String value = mProperties.get(hardwareName);
654                switch (property.getType()) {
655                    case INTEGER:
656                        // intended fall-through.
657                    case DISKSIZE:
658                        return value;
659                    case BOOLEAN:
660                        return HardwareProperties.getBooleanValueIndex(value);
661                    case STRING_ENUM:
662                    case INTEGER_ENUM:
663                        // For a combo, we need to return the index of the value in the enum
664                        String[] values = property.getEnum();
665                        if (values != null) {
666                            for (int i = 0; i < values.length; i++) {
667                                if (values[i].equals(value)) {
668                                    return i;
669                                }
670                            }
671                        }
672                }
673
674                return null;
675            }
676
677            @Override
678            protected CellEditor getCellEditor(Object element) {
679                String hardwareName = (String)element;
680                HardwareProperty property = mHardwareMap.get(hardwareName);
681                switch (property.getType()) {
682                    // TODO: custom TextCellEditor that restrict input.
683                    case INTEGER:
684                        // intended fall-through.
685                    case DISKSIZE:
686                        return new TextCellEditor(hardwareTable);
687                    case BOOLEAN:
688                        return new ComboBoxCellEditor(hardwareTable,
689                                HardwareProperties.BOOLEAN_VALUES,
690                                SWT.READ_ONLY | SWT.DROP_DOWN);
691                    case STRING_ENUM:
692                    case INTEGER_ENUM:
693                        String[] values = property.getEnum();
694                        if (values != null && values.length > 0) {
695                            return new ComboBoxCellEditor(hardwareTable,
696                                    values,
697                                    SWT.READ_ONLY | SWT.DROP_DOWN);
698                        }
699                }
700                return null;
701            }
702
703            @Override
704            protected boolean canEdit(Object element) {
705                String hardwareName = (String)element;
706                HardwareProperty property = mHardwareMap.get(hardwareName);
707                return property != null;
708            }
709        });
710
711
712        mHardwareViewer.setInput(mProperties);
713    }
714
715    // -- Start of internal part ----------
716    // Hide everything down-below from SWT designer
717    //$hide>>$
718
719    /**
720     * When editing an existing AVD info, fill the UI that has just been created with
721     * the values from the AVD.
722     */
723    public void fillExistingAvdInfo() {
724        if (mEditAvdInfo == null) {
725            return;
726        }
727
728        mAvdName.setText(mEditAvdInfo.getName());
729
730        Map<String, String> props = mEditAvdInfo.getProperties();
731
732        IAndroidTarget target = mEditAvdInfo.getTarget();
733        if (target != null && !mCurrentTargets.isEmpty()) {
734            // Try to select the target in the target combo.
735            // This will fail if the AVD needs to be repaired.
736            //
737            // This is a linear search but the list is always
738            // small enough and we only do this once.
739            int n = mTargetCombo.getItemCount();
740            for (int i = 0;i < n; i++) {
741                if (target.equals(mCurrentTargets.get(mTargetCombo.getItem(i)))) {
742                    mTargetCombo.select(i);
743                    reloadAbiTypeCombo();
744                    reloadSkinCombo();
745                    break;
746                }
747            }
748        }
749
750        // select the abi type
751        ISystemImage[] systemImages = getSystemImages(target);
752        if (target != null && systemImages.length > 0) {
753            mAbiTypeCombo.setEnabled(systemImages.length > 1);
754            String abiType = AvdInfo.getPrettyAbiType(mEditAvdInfo.getAbiType());
755            int n = mAbiTypeCombo.getItemCount();
756            for (int i = 0; i < n; i++) {
757                if (abiType.equals(mAbiTypeCombo.getItem(i))) {
758                    mAbiTypeCombo.select(i);
759                    reloadSkinCombo();
760                    break;
761                }
762            }
763        }
764
765        if (props != null) {
766
767            // First try the skin name and if it doesn't work fallback on the skin path
768            nextSkin: for (int s = 0; s < 2; s++) {
769                String skin = props.get(s == 0 ? AvdManager.AVD_INI_SKIN_NAME
770                                               : AvdManager.AVD_INI_SKIN_PATH);
771                if (skin != null && skin.length() > 0) {
772                    Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin);
773                    if (m.matches() && m.groupCount() == 2) {
774                        enableSkinWidgets(false);
775                        mSkinListRadio.setSelection(false);
776                        mSkinSizeRadio.setSelection(true);
777                        mSkinSizeWidth.setText(m.group(1));
778                        mSkinSizeHeight.setText(m.group(2));
779                        break nextSkin;
780                    } else {
781                        enableSkinWidgets(true);
782                        mSkinSizeRadio.setSelection(false);
783                        mSkinListRadio.setSelection(true);
784
785                        int n = mSkinCombo.getItemCount();
786                        for (int i = 0; i < n; i++) {
787                            if (skin.equals(mSkinCombo.getItem(i))) {
788                                mSkinCombo.select(i);
789                                break nextSkin;
790                            }
791                        }
792                    }
793                }
794            }
795
796            String sdcard = props.get(AvdManager.AVD_INI_SDCARD_PATH);
797            if (sdcard != null && sdcard.length() > 0) {
798                enableSdCardWidgets(false);
799                mSdCardSizeRadio.setSelection(false);
800                mSdCardFileRadio.setSelection(true);
801                mSdCardFile.setText(sdcard);
802            }
803
804            sdcard = props.get(AvdManager.AVD_INI_SDCARD_SIZE);
805            if (sdcard != null && sdcard.length() > 0) {
806                String[] values = new String[2];
807                long sdcardSize = AvdManager.parseSdcardSize(sdcard, values);
808
809                if (sdcardSize != AvdManager.SDCARD_NOT_SIZE_PATTERN) {
810                    enableSdCardWidgets(true);
811                    mSdCardFileRadio.setSelection(false);
812                    mSdCardSizeRadio.setSelection(true);
813
814                    mSdCardSize.setText(values[0]);
815
816                    String suffix = values[1];
817                    int n = mSdCardSizeCombo.getItemCount();
818                    for (int i = 0; i < n; i++) {
819                        if (mSdCardSizeCombo.getItem(i).startsWith(suffix)) {
820                            mSdCardSizeCombo.select(i);
821                        }
822                    }
823                }
824            }
825
826            String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
827            if (snapshots != null && snapshots.length() > 0) {
828                mSnapshotCheck.setSelection(snapshots.equals("true"));
829            }
830        }
831
832        mProperties.clear();
833
834        if (props != null) {
835            for (Entry<String, String> entry : props.entrySet()) {
836                HardwareProperty prop = mHardwareMap.get(entry.getKey());
837                if (prop != null && prop.isValidForUi()) {
838                    mProperties.put(entry.getKey(), entry.getValue());
839                }
840            }
841        }
842
843        // Cleanup known non-hardware properties
844        mProperties.remove(AvdManager.AVD_INI_ABI_TYPE);
845        mProperties.remove(AvdManager.AVD_INI_CPU_ARCH);
846        mProperties.remove(AvdManager.AVD_INI_SKIN_PATH);
847        mProperties.remove(AvdManager.AVD_INI_SKIN_NAME);
848        mProperties.remove(AvdManager.AVD_INI_SDCARD_SIZE);
849        mProperties.remove(AvdManager.AVD_INI_SDCARD_PATH);
850        mProperties.remove(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
851        mProperties.remove(AvdManager.AVD_INI_IMAGES_1);
852        mProperties.remove(AvdManager.AVD_INI_IMAGES_2);
853
854        mHardwareViewer.refresh();
855    }
856
857    @Override
858    protected void okPressed() {
859        if (createAvd()) {
860            super.okPressed();
861        }
862    }
863
864    /**
865     * Enable or disable the sd card widgets.
866     * @param sizeMode if true the size-based widgets are to be enabled, and the file-based ones
867     * disabled.
868     */
869    private void enableSdCardWidgets(boolean sizeMode) {
870        mSdCardSize.setEnabled(sizeMode);
871        mSdCardSizeCombo.setEnabled(sizeMode);
872
873        mSdCardFile.setEnabled(!sizeMode);
874        mBrowseSdCard.setEnabled(!sizeMode);
875    }
876
877    /**
878     * Enable or disable the skin widgets.
879     * @param listMode if true the list-based widgets are to be enabled, and the size-based ones
880     * disabled.
881     */
882    private void enableSkinWidgets(boolean listMode) {
883        mSkinCombo.setEnabled(listMode);
884
885        mSkinSizeWidth.setEnabled(!listMode);
886        mSkinSizeHeight.setEnabled(!listMode);
887    }
888
889
890    private void onBrowseSdCard() {
891        FileDialog dlg = new FileDialog(getContents().getShell(), SWT.OPEN);
892        dlg.setText("Choose SD Card image file.");
893
894        String fileName = dlg.open();
895        if (fileName != null) {
896            mSdCardFile.setText(fileName);
897        }
898    }
899
900
901
902    private void reloadTargetCombo() {
903        String selected = null;
904        int index = mTargetCombo.getSelectionIndex();
905        if (index >= 0) {
906            selected = mTargetCombo.getItem(index);
907        }
908
909        mCurrentTargets.clear();
910        mTargetCombo.removeAll();
911
912        boolean found = false;
913        index = -1;
914
915        SdkManager sdkManager = mAvdManager.getSdkManager();
916        if (sdkManager != null) {
917            for (IAndroidTarget target : sdkManager.getTargets()) {
918                String name;
919                if (target.isPlatform()) {
920                    name = String.format("%s - API Level %s",
921                            target.getName(),
922                            target.getVersion().getApiString());
923                } else {
924                    name = String.format("%s (%s) - API Level %s",
925                            target.getName(),
926                            target.getVendor(),
927                            target.getVersion().getApiString());
928                }
929                mCurrentTargets.put(name, target);
930                mTargetCombo.add(name);
931                if (!found) {
932                    index++;
933                    found = name.equals(selected);
934                }
935            }
936        }
937
938        mTargetCombo.setEnabled(mCurrentTargets.size() > 0);
939
940        if (found) {
941            mTargetCombo.select(index);
942        }
943
944        reloadSkinCombo();
945    }
946
947    private void reloadSkinCombo() {
948        String selected = null;
949        int index = mSkinCombo.getSelectionIndex();
950        if (index >= 0) {
951            selected = mSkinCombo.getItem(index);
952        }
953
954        mSkinCombo.removeAll();
955        mSkinCombo.setEnabled(false);
956
957        index = mTargetCombo.getSelectionIndex();
958        if (index >= 0) {
959
960            String targetName = mTargetCombo.getItem(index);
961
962            boolean found = false;
963            IAndroidTarget target = mCurrentTargets.get(targetName);
964            if (target != null) {
965                mSkinCombo.add(String.format("Default (%s)", target.getDefaultSkin()));
966
967                index = -1;
968                for (String skin : target.getSkins()) {
969                    mSkinCombo.add(skin);
970                    if (!found) {
971                        index++;
972                        found = skin.equals(selected);
973                    }
974                }
975
976                mSkinCombo.setEnabled(true);
977
978                if (found) {
979                    mSkinCombo.select(index);
980                } else {
981                    mSkinCombo.select(0);  // default
982                    loadSkin();
983                }
984            }
985        }
986    }
987
988    /**
989    * Reload all the abi types in the selection list
990    */
991    private void reloadAbiTypeCombo() {
992       String selected = null;
993       boolean found = false;
994
995       int index = mTargetCombo.getSelectionIndex();
996       if (index >= 0) {
997           String targetName = mTargetCombo.getItem(index);
998           IAndroidTarget target = mCurrentTargets.get(targetName);
999
1000           ISystemImage[] systemImages = getSystemImages(target);
1001
1002           mAbiTypeCombo.setEnabled(systemImages.length > 1);
1003
1004           // If user explicitly selected an ABI before, preserve that option
1005           // If user did not explicitly select before (only one option before)
1006           // force them to select
1007           index = mAbiTypeCombo.getSelectionIndex();
1008           if (index >= 0 && mAbiTypeCombo.getItemCount() > 1) {
1009               selected = mAbiTypeCombo.getItem(index);
1010           }
1011
1012           mAbiTypeCombo.removeAll();
1013
1014           int i;
1015           for ( i = 0; i < systemImages.length ; i++ ) {
1016               String prettyAbiType = AvdInfo.getPrettyAbiType(systemImages[i].getAbiType());
1017               mAbiTypeCombo.add(prettyAbiType);
1018               if (!found) {
1019                   found = prettyAbiType.equals(selected);
1020                   if (found) {
1021                       mAbiTypeCombo.select(i);
1022                   }
1023               }
1024           }
1025
1026           if (systemImages.length == 1) {
1027               mAbiTypeCombo.select(0);
1028           }
1029       }
1030    }
1031
1032    /**
1033     * Validates the fields, displays errors and warnings.
1034     * Enables the finish button if there are no errors.
1035     */
1036    private void validatePage() {
1037        String error = null;
1038        String warning = null;
1039
1040        // Validate AVD name
1041        String avdName = mAvdName.getText().trim();
1042        boolean hasAvdName = avdName.length() > 0;
1043        boolean isCreate = mEditAvdInfo == null || !avdName.equals(mEditAvdInfo.getName());
1044
1045        if (hasAvdName && !AvdManager.RE_AVD_NAME.matcher(avdName).matches()) {
1046            error = String.format(
1047                "AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
1048                avdName, AvdManager.CHARS_AVD_NAME);
1049        }
1050
1051        // Validate target
1052        if (hasAvdName && error == null && mTargetCombo.getSelectionIndex() < 0) {
1053            error = "A target must be selected in order to create an AVD.";
1054        }
1055
1056        // validate abi type if the selected target supports multi archs.
1057        if (hasAvdName && error == null && mTargetCombo.getSelectionIndex() > 0) {
1058            int index = mTargetCombo.getSelectionIndex();
1059            String targetName = mTargetCombo.getItem(index);
1060            IAndroidTarget target = mCurrentTargets.get(targetName);
1061            ISystemImage[] systemImages = getSystemImages(target);
1062            if (systemImages.length > 1 && mAbiTypeCombo.getSelectionIndex() < 0) {
1063               error = "An ABI type must be selected in order to create an AVD.";
1064            }
1065        }
1066
1067        // Validate SDCard path or value
1068        if (error == null) {
1069            // get the mode. We only need to check the file since the
1070            // verifier on the size Text will prevent invalid input
1071            boolean sdcardFileMode = mSdCardFileRadio.getSelection();
1072            if (sdcardFileMode) {
1073                String sdName = mSdCardFile.getText().trim();
1074                if (sdName.length() > 0 && !new File(sdName).isFile()) {
1075                    error = "SD Card path isn't valid.";
1076                }
1077            } else {
1078                String valueString = mSdCardSize.getText();
1079                if (valueString.length() > 0) {
1080                    long value = 0;
1081                    try {
1082                        value = Long.parseLong(valueString);
1083
1084                        int sizeIndex = mSdCardSizeCombo.getSelectionIndex();
1085                        if (sizeIndex >= 0) {
1086                            // index 0 shifts by 10 (1024=K), index 1 by 20, etc.
1087                            value <<= 10*(1 + sizeIndex);
1088                        }
1089
1090                        if (value < AvdManager.SDCARD_MIN_BYTE_SIZE ||
1091                                value > AvdManager.SDCARD_MAX_BYTE_SIZE) {
1092                            value = 0;
1093                        }
1094                    } catch (Exception e) {
1095                        // ignore, we'll test value below.
1096                    }
1097                    if (value <= 0) {
1098                        error = "SD Card size is invalid. Range is 9 MiB..1023 GiB.";
1099                    } else if (mEditAvdInfo != null) {
1100                        // When editing an existing AVD, compare with the existing
1101                        // sdcard size, if any. It only matters if there was an sdcard setting
1102                        // before.
1103                        Map<String, String> props = mEditAvdInfo.getProperties();
1104                        if (props != null) {
1105                            String original =
1106                                mEditAvdInfo.getProperties().get(AvdManager.AVD_INI_SDCARD_SIZE);
1107                            if (original != null && original.length() > 0) {
1108                                long originalSize =
1109                                    AvdManager.parseSdcardSize(original, null/*parsedStrings*/);
1110                                if (originalSize > 0 && value != originalSize) {
1111                                    warning = "A new SD Card file will be created.\nThe current SD Card file will be lost.";
1112                                }
1113                            }
1114                        }
1115                    }
1116                }
1117            }
1118        }
1119
1120        // validate the skin
1121        if (error == null) {
1122            // get the mode, we should only check if it's in size mode since
1123            // the built-in list mode is always valid.
1124            if (mSkinSizeRadio.getSelection()) {
1125                // need both with and heigh to be non null
1126                String width = mSkinSizeWidth.getText();   // no need for trim, since the verifier
1127                String height = mSkinSizeHeight.getText(); // rejects non digit.
1128
1129                if (width.length() == 0 || height.length() == 0) {
1130                    error = "Skin size is incorrect.\nBoth dimensions must be > 0.";
1131                }
1132            }
1133        }
1134
1135        // Check for duplicate AVD name
1136        if (isCreate && hasAvdName && error == null && !mForceCreation.getSelection()) {
1137            Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(avdName);
1138            assert conflict != null;
1139            switch(conflict.getFirst()) {
1140            case NO_CONFLICT:
1141                break;
1142            case CONFLICT_EXISTING_AVD:
1143            case CONFLICT_INVALID_AVD:
1144                error = String.format(
1145                        "The AVD name '%s' is already used.\n" +
1146                        "Check \"Override the existing AVD\" to delete the existing one.",
1147                        avdName);
1148                break;
1149            case CONFLICT_EXISTING_PATH:
1150                error = String.format(
1151                        "Conflict with %s\n" +
1152                        "Check \"Override the existing AVD\" to delete the existing one.",
1153                        conflict.getSecond());
1154                break;
1155            default:
1156                // Hmm not supposed to happen... probably someone expanded the
1157                // enum without adding something here. In this case just do an
1158                // assert and use a generic error message.
1159                error = String.format(
1160                        "Conflict %s with %s.\n" +
1161                        "Check \"Override the existing AVD\" to delete the existing one.",
1162                        conflict.getFirst().toString(),
1163                        conflict.getSecond());
1164                assert false;
1165                break;
1166            }
1167        }
1168
1169        if (error == null && mEditAvdInfo != null && isCreate) {
1170            warning = String.format("The AVD '%1$s' will be duplicated into '%2$s'.",
1171                    mEditAvdInfo.getName(),
1172                    avdName);
1173        }
1174
1175        // Validate the create button
1176        boolean can_create = hasAvdName && error == null;
1177        if (can_create) {
1178            can_create &= mTargetCombo.getSelectionIndex() >= 0;
1179        }
1180        mOkButton.setEnabled(can_create);
1181
1182        // Adjust the create button label as needed
1183        if (isCreate) {
1184            mOkButton.setText("Create AVD");
1185        } else {
1186            mOkButton.setText("Edit AVD");
1187        }
1188
1189        // -- update UI
1190        if (error != null) {
1191            mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png"));  //$NON-NLS-1$
1192            mStatusLabel.setText(error);
1193        } else if (warning != null) {
1194            mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png"));  //$NON-NLS-1$
1195            mStatusLabel.setText(warning);
1196        } else {
1197            mStatusIcon.setImage(null);
1198            mStatusLabel.setText(" \n "); //$NON-NLS-1$
1199        }
1200
1201        mStatusComposite.pack(true);
1202    }
1203
1204    private void loadSkin() {
1205        int targetIndex = mTargetCombo.getSelectionIndex();
1206        if (targetIndex < 0) {
1207            return;
1208        }
1209
1210        // resolve the target.
1211        String targetName = mTargetCombo.getItem(targetIndex);
1212        IAndroidTarget target = mCurrentTargets.get(targetName);
1213        if (target == null) {
1214            return;
1215        }
1216
1217        // get the skin name
1218        String skinName = null;
1219        int skinIndex = mSkinCombo.getSelectionIndex();
1220        if (skinIndex < 0) {
1221            return;
1222        } else if (skinIndex == 0) { // default skin for the target
1223            skinName = target.getDefaultSkin();
1224        } else {
1225            skinName = mSkinCombo.getItem(skinIndex);
1226        }
1227
1228        // load the skin properties
1229        String path = target.getPath(IAndroidTarget.SKINS);
1230        File skin = new File(path, skinName);
1231        if (skin.isDirectory() == false && target.isPlatform() == false) {
1232            // it's possible the skin is in the parent target
1233            path = target.getParent().getPath(IAndroidTarget.SKINS);
1234            skin = new File(path, skinName);
1235        }
1236
1237        if (skin.isDirectory() == false) {
1238            return;
1239        }
1240
1241        // now get the hardware.ini from the add-on (if applicable) and from the skin
1242        // (if applicable)
1243        HashMap<String, String> hardwareValues = new HashMap<String, String>();
1244        if (target.isPlatform() == false) {
1245            FileWrapper targetHardwareFile = new FileWrapper(target.getLocation(),
1246                    AvdManager.HARDWARE_INI);
1247            if (targetHardwareFile.isFile()) {
1248                Map<String, String> targetHardwareConfig = ProjectProperties.parsePropertyFile(
1249                        targetHardwareFile, null /*log*/);
1250
1251                if (targetHardwareConfig != null) {
1252                    hardwareValues.putAll(targetHardwareConfig);
1253                }
1254            }
1255        }
1256
1257        // from the skin
1258        FileWrapper skinHardwareFile = new FileWrapper(skin, AvdManager.HARDWARE_INI);
1259        if (skinHardwareFile.isFile()) {
1260            Map<String, String> skinHardwareConfig = ProjectProperties.parsePropertyFile(
1261                    skinHardwareFile, null /*log*/);
1262
1263            if (skinHardwareConfig != null) {
1264                hardwareValues.putAll(skinHardwareConfig);
1265            }
1266        }
1267
1268        // now set those values in the list of properties for the AVD.
1269        // We just check that none of those properties have been edited by the user yet.
1270        for (Entry<String, String> entry : hardwareValues.entrySet()) {
1271            if (mEditedProperties.contains(entry.getKey()) == false) {
1272                mProperties.put(entry.getKey(), entry.getValue());
1273            }
1274        }
1275
1276        mHardwareViewer.refresh();
1277    }
1278
1279    /**
1280     * Creates an AVD from the values in the UI. Called when the user presses the OK button.
1281     */
1282    private boolean createAvd() {
1283        String avdName = mAvdName.getText().trim();
1284        int index = mTargetCombo.getSelectionIndex();
1285
1286        // quick check on the name and the target selection
1287        if (avdName.length() == 0 || index < 0) {
1288            return false;
1289        }
1290
1291        // resolve the target.
1292        String targetName = mTargetCombo.getItem(index);
1293        IAndroidTarget target = mCurrentTargets.get(targetName);
1294        if (target == null) {
1295            return false;
1296        }
1297
1298        // get the abi type
1299        mAbiType = SdkConstants.ABI_ARMEABI;
1300        ISystemImage[] systemImages = getSystemImages(target);
1301        if (systemImages.length > 0) {
1302            int abiIndex = mAbiTypeCombo.getSelectionIndex();
1303            if (abiIndex >= 0) {
1304                String prettyname = mAbiTypeCombo.getItem(abiIndex);
1305                //Extract the abi type
1306                int firstIndex = prettyname.indexOf("(");
1307                int lastIndex = prettyname.indexOf(")");
1308                mAbiType = prettyname.substring(firstIndex+1, lastIndex);
1309            }
1310        }
1311
1312        // get the SD card data from the UI.
1313        String sdName = null;
1314        if (mSdCardSizeRadio.getSelection()) {
1315            // size mode
1316            String value = mSdCardSize.getText().trim();
1317            if (value.length() > 0) {
1318                sdName = value;
1319                // add the unit
1320                switch (mSdCardSizeCombo.getSelectionIndex()) {
1321                    case 0:
1322                        sdName += "K";  //$NON-NLS-1$
1323                        break;
1324                    case 1:
1325                        sdName += "M";  //$NON-NLS-1$
1326                        break;
1327                    case 2:
1328                        sdName += "G";  //$NON-NLS-1$
1329                        break;
1330                    default:
1331                        // shouldn't be here
1332                        assert false;
1333                }
1334            }
1335        } else {
1336            // file mode.
1337            sdName = mSdCardFile.getText().trim();
1338        }
1339
1340        // get the Skin data from the UI
1341        String skinName = null;
1342        if (mSkinListRadio.getSelection()) {
1343            // built-in list provides the skin
1344            int skinIndex = mSkinCombo.getSelectionIndex();
1345            if (skinIndex > 0) {
1346                // index 0 is the default, we don't use it
1347                skinName = mSkinCombo.getItem(skinIndex);
1348            }
1349        } else {
1350            // size mode. get both size and writes it as a skin name
1351            // thanks to validatePage() we know the content of the fields is correct
1352            skinName = mSkinSizeWidth.getText() + "x" + mSkinSizeHeight.getText(); //$NON-NLS-1$
1353        }
1354
1355        ILogger log = mSdkLog;
1356        if (log == null || log instanceof MessageBoxLog) {
1357            // If the current logger is a message box, we use our own (to make sure
1358            // to display errors right away and customize the title).
1359            log = new MessageBoxLog(
1360                    String.format("Result of creating AVD '%s':", avdName),
1361                    getContents().getDisplay(),
1362                    false /*logErrorsOnly*/);
1363        }
1364
1365        File avdFolder = null;
1366        try {
1367            avdFolder = AvdInfo.getDefaultAvdFolder(mAvdManager, avdName);
1368        } catch (AndroidLocationException e) {
1369            return false;
1370        }
1371
1372        boolean force = mForceCreation.getSelection();
1373        boolean snapshot = mSnapshotCheck.getSelection();
1374
1375        boolean success = false;
1376        AvdInfo avdInfo = mAvdManager.createAvd(
1377                avdFolder,
1378                avdName,
1379                target,
1380                mAbiType,
1381                skinName,
1382                sdName,
1383                mProperties,
1384                snapshot,
1385                force,
1386                mEditAvdInfo != null, //edit existing
1387                log);
1388
1389        success = avdInfo != null;
1390
1391        if (log instanceof MessageBoxLog) {
1392            ((MessageBoxLog) log).displayResult(success);
1393        }
1394        return success;
1395    }
1396
1397    /**
1398     * Returns the list of system images of a target.
1399     * <p/>
1400     * If target is null, returns an empty list.
1401     * If target is an add-on with no system images, return the list from its parent platform.
1402     *
1403     * @param target An IAndroidTarget. Can be null.
1404     * @return A non-null ISystemImage array. Can be empty.
1405     */
1406    private ISystemImage[] getSystemImages(IAndroidTarget target) {
1407        if (target != null) {
1408            ISystemImage[] images = target.getSystemImages();
1409
1410            if ((images == null || images.length == 0) && !target.isPlatform()) {
1411                // If an add-on does not provide any system images, use the ones from the parent.
1412                images = target.getParent().getSystemImages();
1413            }
1414
1415            if (images != null) {
1416                return images;
1417            }
1418        }
1419
1420        return new ISystemImage[0];
1421    }
1422
1423    // End of hiding from SWT Designer
1424    //$hide<<$
1425}
1426