1/*
2 * Copyright (C) 2012 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.prefs.AndroidLocation.AndroidLocationException;
21import com.android.resources.Density;
22import com.android.resources.ScreenSize;
23import com.android.sdklib.IAndroidTarget;
24import com.android.sdklib.ISystemImage;
25import com.android.sdklib.SdkManager;
26import com.android.sdklib.devices.Camera;
27import com.android.sdklib.devices.CameraLocation;
28import com.android.sdklib.devices.Device;
29import com.android.sdklib.devices.DeviceManager;
30import com.android.sdklib.devices.Hardware;
31import com.android.sdklib.devices.Screen;
32import com.android.sdklib.devices.Storage;
33import com.android.sdklib.internal.avd.AvdInfo;
34import com.android.sdklib.internal.avd.AvdManager;
35import com.android.sdklib.internal.avd.AvdManager.AvdConflict;
36import com.android.sdklib.internal.avd.HardwareProperties;
37import com.android.sdkuilib.internal.repository.icons.ImageFactory;
38import com.android.sdkuilib.ui.GridDialog;
39import com.android.utils.ILogger;
40import com.android.utils.Pair;
41
42import org.eclipse.jface.dialogs.IDialogConstants;
43import org.eclipse.swt.SWT;
44import org.eclipse.swt.events.ModifyEvent;
45import org.eclipse.swt.events.ModifyListener;
46import org.eclipse.swt.events.SelectionAdapter;
47import org.eclipse.swt.events.SelectionEvent;
48import org.eclipse.swt.events.VerifyEvent;
49import org.eclipse.swt.events.VerifyListener;
50import org.eclipse.swt.layout.GridData;
51import org.eclipse.swt.layout.GridLayout;
52import org.eclipse.swt.widgets.Button;
53import org.eclipse.swt.widgets.Combo;
54import org.eclipse.swt.widgets.Composite;
55import org.eclipse.swt.widgets.Control;
56import org.eclipse.swt.widgets.FileDialog;
57import org.eclipse.swt.widgets.Group;
58import org.eclipse.swt.widgets.Label;
59import org.eclipse.swt.widgets.Shell;
60import org.eclipse.swt.widgets.Text;
61
62import java.io.File;
63import java.util.ArrayList;
64import java.util.List;
65import java.util.Map;
66import java.util.TreeMap;
67
68public class AvdCreationDialog extends GridDialog {
69
70    private AvdManager mAvdManager;
71    private ImageFactory mImageFactory;
72    private ILogger mSdkLog;
73    private AvdInfo mAvdInfo;
74
75    // A map from manufacturers to their list of devices.
76    private Map<String, List<Device>> mDeviceMap;
77    private final TreeMap<String, IAndroidTarget> mCurrentTargets =
78            new TreeMap<String, IAndroidTarget>();
79
80    private Button mOkButton;
81
82    private Text mAvdName;
83
84    private Combo mDeviceManufacturer;
85    private Combo mDeviceName;
86
87    private Combo mTarget;
88    private Combo mAbi;
89
90    private Combo mFrontCamera;
91    private Combo mBackCamera;
92
93    private Button mSnapshot;
94    private Button mGpuEmulation;
95
96    private Text mRam;
97    private Text mVmHeap;
98
99    private Text mDataPartition;
100    private Combo mDataPartitionSize;
101
102    private Button mSdCardSizeRadio;
103    private Text mSdCardSize;
104    private Combo mSdCardSizeCombo;
105    private Button mSdCardFileRadio;
106    private Text mSdCardFile;
107    private Button mBrowseSdCard;
108
109    private Button mForceCreation;
110    private Composite mStatusComposite;
111
112    private Label mStatusIcon;
113    private Label mStatusLabel;
114
115    /**
116     * {@link VerifyListener} for {@link Text} widgets that should only contains
117     * numbers.
118     */
119    private final VerifyListener mDigitVerifier = new VerifyListener() {
120        @Override
121        public void verifyText(VerifyEvent event) {
122            int count = event.text.length();
123            for (int i = 0; i < count; i++) {
124                char c = event.text.charAt(i);
125                if (c < '0' || c > '9') {
126                    event.doit = false;
127                    return;
128                }
129            }
130        }
131    };
132
133    public AvdCreationDialog(Shell shell,
134            AvdManager avdManager,
135            ImageFactory imageFactory,
136            ILogger log,
137            AvdInfo editAvdInfo) {
138
139        super(shell, 2, false);
140        mAvdManager = avdManager;
141        mImageFactory = imageFactory;
142        mSdkLog = log;
143        mAvdInfo = editAvdInfo;
144
145        mDeviceMap = new TreeMap<String, List<Device>>();
146
147        SdkManager sdkMan = avdManager.getSdkManager();
148        if (sdkMan != null && sdkMan.getLocation() != null) {
149            List<Device> devices = (new DeviceManager(log)).getDevices(sdkMan.getLocation());
150            for (Device d : devices) {
151                List<Device> list;
152                if (mDeviceMap.containsKey(d.getManufacturer())) {
153                    list = mDeviceMap.get(d.getManufacturer());
154                } else {
155                    list = new ArrayList<Device>();
156                    mDeviceMap.put(d.getManufacturer(), list);
157                }
158                list.add(d);
159            }
160        }
161    }
162
163    @Override
164    protected Control createContents(Composite parent) {
165        Control control = super.createContents(parent);
166        getShell().setText(mAvdInfo == null ? "Create new Android Virtual Device (AVD)"
167                                            : "Edit Android Virtual Device (AVD)");
168
169        mOkButton = getButton(IDialogConstants.OK_ID);
170
171        if (mAvdInfo != null) {
172            fillExistingAvdInfo(mAvdInfo);
173        }
174
175        validatePage();
176        return control;
177    }
178
179    @Override
180    public void createDialogContent(Composite parent) {
181
182        Label label;
183        String tooltip;
184        ValidateListener validateListener = new ValidateListener();
185
186        // --- avd name
187        label = new Label(parent, SWT.NONE);
188        label.setText("AVD Name:");
189        tooltip = "The name of the Android Virtual Device";
190        label.setToolTipText(tooltip);
191        mAvdName = new Text(parent, SWT.BORDER);
192        mAvdName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
193        mAvdName.addModifyListener(new CreateNameModifyListener());
194
195        // --- device selection
196        label = new Label(parent, SWT.NONE);
197        label.setText("Device\nManufacturer:");
198        tooltip = "The manufacturer of the device this AVD will be based on";
199        mDeviceManufacturer = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
200        for (String manufacturer : mDeviceMap.keySet()) {
201            mDeviceManufacturer.add(manufacturer);
202        }
203        mDeviceManufacturer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
204        mDeviceManufacturer.addSelectionListener(new SelectionAdapter() {
205            @Override
206            public void widgetSelected(SelectionEvent e) {
207                reloadDeviceNameCombo();
208                validatePage();
209            }
210        });
211
212        label = new Label(parent, SWT.NONE);
213        label.setText("Device Name:");
214        tooltip = "The name of the device this AVD will be based on";
215        mDeviceName = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
216        mDeviceName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
217        mDeviceName.addSelectionListener(new DeviceSelectionListener());
218
219        // --- api target
220        label = new Label(parent, SWT.NONE);
221        label.setText("Target:");
222        tooltip = "The target API of the AVD";
223        label.setToolTipText(tooltip);
224        mTarget = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
225        mTarget.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
226        mTarget.setToolTipText(tooltip);
227        mTarget.addSelectionListener(new SelectionAdapter() {
228            @Override
229            public void widgetSelected(SelectionEvent e) {
230                reloadAbiTypeCombo();
231                validatePage();
232            }
233        });
234
235        reloadTargetCombo();
236
237        // --- avd ABIs
238        label = new Label(parent, SWT.NONE);
239        label.setText("CPU/ABI:");
240        tooltip = "The CPU/ABI of the virtual device";
241        label.setToolTipText(tooltip);
242        mAbi = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
243        mAbi.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
244        mAbi.setToolTipText(tooltip);
245        mAbi.addSelectionListener(validateListener);
246
247        label = new Label(parent, SWT.NONE);
248        label.setText("Front Camera:");
249        tooltip = "";
250        label.setToolTipText(tooltip);
251        mFrontCamera = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
252        mFrontCamera.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
253        mFrontCamera.add("None");
254        mFrontCamera.add("Emulated");
255        mFrontCamera.add("Webcam0");
256        mFrontCamera.select(0);
257
258        label = new Label(parent, SWT.NONE);
259        label.setText("Back Camera:");
260        tooltip = "";
261        label.setToolTipText(tooltip);
262        mBackCamera = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
263        mBackCamera.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
264        mBackCamera.add("None");
265        mBackCamera.add("Emulated");
266        mBackCamera.add("Webcam0");
267        mBackCamera.select(0);
268
269        toggleCameras();
270
271        // --- memory options group
272        label = new Label(parent, SWT.NONE);
273        label.setText("Memory Options:");
274
275
276        Group memoryGroup = new Group(parent, SWT.BORDER);
277        memoryGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
278        memoryGroup.setLayout(new GridLayout(4, false));
279
280        label = new Label(memoryGroup, SWT.NONE);
281        label.setText("RAM:");
282        tooltip = "The amount of RAM the emulated device should have in MiB";
283        label.setToolTipText(tooltip);
284        mRam = new Text(memoryGroup, SWT.BORDER);
285        mRam.addVerifyListener(mDigitVerifier);
286        mRam.addModifyListener(validateListener);
287        mRam.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
288
289        label = new Label(memoryGroup, SWT.NONE);
290        label.setText("VM Heap:");
291        tooltip = "The amount of memory, in MiB, available to typical Android applications";
292        label.setToolTipText(tooltip);
293        mVmHeap = new Text(memoryGroup, SWT.BORDER);
294        mVmHeap.addVerifyListener(mDigitVerifier);
295        mVmHeap.addModifyListener(validateListener);
296        mVmHeap.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
297        mVmHeap.setToolTipText(tooltip);
298
299        // --- Data partition group
300        label = new Label(parent, SWT.NONE);
301        label.setText("Internal Storage:");
302        tooltip = "The size of the data partition on the device.";
303        Group storageGroup = new Group(parent, SWT.NONE);
304        storageGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
305        storageGroup.setLayout(new GridLayout(2, false));
306        mDataPartition = new Text(storageGroup, SWT.BORDER);
307        mDataPartition.setText("200");
308        mDataPartition.addVerifyListener(mDigitVerifier);
309        mDataPartition.addModifyListener(validateListener);
310        mDataPartition.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
311        mDataPartitionSize = new Combo(storageGroup, SWT.READ_ONLY | SWT.DROP_DOWN);
312        mDataPartitionSize.add("MiB");
313        mDataPartitionSize.add("GiB");
314        mDataPartitionSize.select(0);
315        mDataPartitionSize.addModifyListener(validateListener);
316
317        // --- sd card group
318        label = new Label(parent, SWT.NONE);
319        label.setText("SD Card:");
320        label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
321                false, false));
322
323        final Group sdCardGroup = new Group(parent, SWT.NONE);
324        sdCardGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
325        sdCardGroup.setLayout(new GridLayout(3, false));
326
327        mSdCardSizeRadio = new Button(sdCardGroup, SWT.RADIO);
328        mSdCardSizeRadio.setText("Size:");
329        mSdCardSizeRadio.setToolTipText("Create a new SD Card file");
330        mSdCardSizeRadio.addSelectionListener(new SelectionAdapter() {
331            @Override
332            public void widgetSelected(SelectionEvent arg0) {
333                boolean sizeMode = mSdCardSizeRadio.getSelection();
334                enableSdCardWidgets(sizeMode);
335                validatePage();
336            }
337        });
338
339        mSdCardSize = new Text(sdCardGroup, SWT.BORDER);
340        mSdCardSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
341        mSdCardSize.addVerifyListener(mDigitVerifier);
342        mSdCardSize.addModifyListener(validateListener);
343        mSdCardSize.setToolTipText("Size of the new SD Card file (must be at least 9 MiB)");
344
345        mSdCardSizeCombo = new Combo(sdCardGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
346        mSdCardSizeCombo.add("KiB");
347        mSdCardSizeCombo.add("MiB");
348        mSdCardSizeCombo.add("GiB");
349        mSdCardSizeCombo.select(1);
350        mSdCardSizeCombo.addSelectionListener(validateListener);
351
352        mSdCardFileRadio = new Button(sdCardGroup, SWT.RADIO);
353        mSdCardFileRadio.setText("File:");
354        mSdCardFileRadio.setToolTipText("Use an existing file for the SD Card");
355
356        mSdCardFile = new Text(sdCardGroup, SWT.BORDER);
357        mSdCardFile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
358        mSdCardFile.addModifyListener(validateListener);
359        mSdCardFile.setToolTipText("File to use for the SD Card");
360
361        mBrowseSdCard = new Button(sdCardGroup, SWT.PUSH);
362        mBrowseSdCard.setText("Browse...");
363        mBrowseSdCard.setToolTipText("Select the file to use for the SD Card");
364        mBrowseSdCard.addSelectionListener(new SelectionAdapter() {
365            @Override
366            public void widgetSelected(SelectionEvent arg0) {
367                onBrowseSdCard();
368                validatePage();
369            }
370        });
371
372        mSdCardSizeRadio.setSelection(true);
373        enableSdCardWidgets(true);
374
375        // --- avd options group
376        label = new Label(parent, SWT.NONE);
377        label.setText("Emulation Options:");
378        Group optionsGroup = new Group(parent, SWT.NONE);
379        optionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
380        optionsGroup.setLayout(new GridLayout(2, true));
381        mSnapshot = new Button(optionsGroup, SWT.CHECK);
382        mSnapshot.setText("Snapshot");
383        mSnapshot.setToolTipText("Emulator's state will be persisted between emulator executions");
384        mSnapshot.addSelectionListener(validateListener);
385        mGpuEmulation = new Button(optionsGroup, SWT.CHECK);
386        mGpuEmulation.setText("GPU Emulation");
387        mGpuEmulation.setToolTipText("Enable hardware OpenGLES emulation");
388        mGpuEmulation.addSelectionListener(validateListener);
389
390        // --- force creation group
391        mForceCreation = new Button(parent, SWT.CHECK);
392        mForceCreation.setText("Override the existing AVD with the same name");
393        mForceCreation
394                .setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD.");
395        mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER,
396                true, false, 2, 1));
397        mForceCreation.setEnabled(false);
398        mForceCreation.addSelectionListener(validateListener);
399
400        // add a separator to separate from the ok/cancel button
401        label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
402        label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
403
404        // add stuff for the error display
405        mStatusComposite = new Composite(parent, SWT.NONE);
406        mStatusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
407                true, false, 3, 1));
408        GridLayout gl;
409        mStatusComposite.setLayout(gl = new GridLayout(2, false));
410        gl.marginHeight = gl.marginWidth = 0;
411
412        mStatusIcon = new Label(mStatusComposite, SWT.NONE);
413        mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
414                false, false));
415        mStatusLabel = new Label(mStatusComposite, SWT.NONE);
416        mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
417        mStatusLabel.setText(""); //$NON-NLS-1$
418
419    }
420
421    /**
422     * {@link ModifyListener} used for live-validation of the fields content.
423     */
424    private class ValidateListener extends SelectionAdapter implements ModifyListener {
425        @Override
426        public void modifyText(ModifyEvent e) {
427            validatePage();
428        }
429
430        @Override
431        public void widgetSelected(SelectionEvent e) {
432            super.widgetSelected(e);
433            validatePage();
434        }
435    }
436
437    /**
438     * Callback when the AVD name is changed. When creating a new AVD, enables
439     * the force checkbox if the name is a duplicate. When editing an existing
440     * AVD, it's OK for the name to match the existing AVD.
441     */
442    private class CreateNameModifyListener implements ModifyListener {
443        @Override
444        public void modifyText(ModifyEvent e) {
445            String name = mAvdName.getText().trim();
446            if (mAvdInfo == null || !name.equals(mAvdInfo.getName())) {
447                // Case where we're creating a new AVD or editing an existing
448                // one
449                // and the AVD name has been changed... check for name
450                // uniqueness.
451
452                Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(name);
453                if (conflict.getFirst() != AvdManager.AvdConflict.NO_CONFLICT) {
454                    // If we're changing the state from disabled to enabled,
455                    // make sure
456                    // to uncheck the button, to force the user to voluntarily
457                    // re-enforce it.
458                    // This happens when editing an existing AVD and changing
459                    // the name from
460                    // the existing AVD to another different existing AVD.
461                    if (!mForceCreation.isEnabled()) {
462                        mForceCreation.setEnabled(true);
463                        mForceCreation.setSelection(false);
464                    }
465                } else {
466                    mForceCreation.setEnabled(false);
467                    mForceCreation.setSelection(false);
468                }
469            } else {
470                // Case where we're editing an existing AVD with the name
471                // unchanged.
472
473                mForceCreation.setEnabled(false);
474                mForceCreation.setSelection(false);
475            }
476            validatePage();
477        }
478    }
479
480    private class DeviceSelectionListener extends SelectionAdapter {
481
482        @Override
483        public void widgetSelected(SelectionEvent arg0) {
484            Device currentDevice = null;
485            for (Device d : mDeviceMap.get(mDeviceManufacturer.getText())) {
486                if (d.getName().equals(mDeviceName.getText())) {
487                    currentDevice = d;
488                    break;
489                }
490            }
491
492            if (currentDevice != null) {
493                Hardware hw = currentDevice.getDefaultHardware();
494                Long ram = hw.getRam().getSizeAsUnit(Storage.Unit.MiB);
495                mRam.setText(Long.toString(ram));
496
497                // Set the default VM heap size. This is based on the Android CDD minimums for each
498                // screen size and density.
499                Screen s = hw.getScreen();
500                ScreenSize size = s.getSize();
501                Density density = s.getPixelDensity();
502                int vmHeapSize = 32;
503                if (size.equals(ScreenSize.XLARGE)) {
504                    switch (density) {
505                        case LOW:
506                        case MEDIUM:
507                            vmHeapSize = 32;
508                            break;
509                        case TV:
510                        case HIGH:
511                            vmHeapSize = 64;
512                            break;
513                        case XHIGH:
514                        case XXHIGH:
515                            vmHeapSize = 128;
516                    }
517                } else {
518                    switch (density) {
519                        case LOW:
520                        case MEDIUM:
521                            vmHeapSize = 16;
522                            break;
523                        case TV:
524                        case HIGH:
525                            vmHeapSize = 32;
526                            break;
527                        case XHIGH:
528                        case XXHIGH:
529                            vmHeapSize = 64;
530
531                    }
532                }
533                mVmHeap.setText(Integer.toString(vmHeapSize));
534            }
535
536            toggleCameras();
537            validatePage();
538        }
539
540    }
541
542    private void toggleCameras() {
543        mFrontCamera.setEnabled(false);
544        mBackCamera.setEnabled(false);
545        if (mDeviceName.getSelectionIndex() >= 0) {
546            List<Device> devices = mDeviceMap.get(mDeviceManufacturer.getText());
547            for (Device d : devices) {
548                if (mDeviceName.getText().equals(d.getName())) {
549                    for (Camera c : d.getDefaultHardware().getCameras()) {
550                        if (CameraLocation.FRONT.equals(c.getLocation())) {
551                            mFrontCamera.setEnabled(true);
552                        }
553                        if (CameraLocation.BACK.equals(c.getLocation())) {
554                            mBackCamera.setEnabled(true);
555                        }
556                    }
557                }
558            }
559
560        }
561    }
562
563    private void reloadDeviceNameCombo() {
564        mDeviceName.removeAll();
565        if (mDeviceMap.containsKey(mDeviceManufacturer.getText())) {
566            for (final Device d : mDeviceMap.get(mDeviceManufacturer.getText())) {
567                mDeviceName.add(d.getName());
568            }
569        }
570
571    }
572
573    private void reloadTargetCombo() {
574        String selected = null;
575        int index = mTarget.getSelectionIndex();
576        if (index >= 0) {
577            selected = mTarget.getItem(index);
578        }
579
580        mCurrentTargets.clear();
581        mTarget.removeAll();
582
583        boolean found = false;
584        index = -1;
585
586        SdkManager sdkManager = mAvdManager.getSdkManager();
587        if (sdkManager != null) {
588            for (IAndroidTarget target : sdkManager.getTargets()) {
589                String name;
590                if (target.isPlatform()) {
591                    name = String.format("%s - API Level %s",
592                            target.getName(),
593                            target.getVersion().getApiString());
594                } else {
595                    name = String.format("%s (%s) - API Level %s",
596                            target.getName(),
597                            target.getVendor(),
598                            target.getVersion().getApiString());
599                }
600                mCurrentTargets.put(name, target);
601                mTarget.add(name);
602                if (!found) {
603                    index++;
604                    found = name.equals(selected);
605                }
606            }
607        }
608
609        mTarget.setEnabled(mCurrentTargets.size() > 0);
610
611        if (found) {
612            mTarget.select(index);
613        }
614    }
615
616    /**
617     * Reload all the abi types in the selection list
618     */
619    private void reloadAbiTypeCombo() {
620        String selected = null;
621        boolean found = false;
622
623        int index = mTarget.getSelectionIndex();
624        if (index >= 0) {
625            String targetName = mTarget.getItem(index);
626            IAndroidTarget target = mCurrentTargets.get(targetName);
627
628            ISystemImage[] systemImages = getSystemImages(target);
629
630            mAbi.setEnabled(systemImages.length > 1);
631
632            // If user explicitly selected an ABI before, preserve that option
633            // If user did not explicitly select before (only one option before)
634            // force them to select
635            index = mAbi.getSelectionIndex();
636            if (index >= 0 && mAbi.getItemCount() > 1) {
637                selected = mAbi.getItem(index);
638            }
639
640            mAbi.removeAll();
641
642            int i;
643            for (i = 0; i < systemImages.length; i++) {
644                String prettyAbiType = AvdInfo.getPrettyAbiType(systemImages[i].getAbiType());
645                mAbi.add(prettyAbiType);
646                if (!found) {
647                    found = prettyAbiType.equals(selected);
648                    if (found) {
649                        mAbi.select(i);
650                    }
651                }
652            }
653
654            if (systemImages.length == 1) {
655                mAbi.select(0);
656            }
657        }
658    }
659
660    /**
661     * Enable or disable the sd card widgets.
662     *
663     * @param sizeMode if true the size-based widgets are to be enabled, and the
664     *            file-based ones disabled.
665     */
666    private void enableSdCardWidgets(boolean sizeMode) {
667        mSdCardSize.setEnabled(sizeMode);
668        mSdCardSizeCombo.setEnabled(sizeMode);
669
670        mSdCardFile.setEnabled(!sizeMode);
671        mBrowseSdCard.setEnabled(!sizeMode);
672    }
673
674    private void onBrowseSdCard() {
675        FileDialog dlg = new FileDialog(getContents().getShell(), SWT.OPEN);
676        dlg.setText("Choose SD Card image file.");
677
678        String fileName = dlg.open();
679        if (fileName != null) {
680            mSdCardFile.setText(fileName);
681        }
682    }
683
684    @Override
685    public void okPressed() {
686        if (createAvd()) {
687            super.okPressed();
688        }
689    }
690
691    private void validatePage() {
692        String error = null;
693        String warning = null;
694        boolean valid = true;
695        if (mAvdName.getText().isEmpty()) {
696            valid = false;
697        }
698
699        if (mDeviceManufacturer.getSelectionIndex() < 0 || mDeviceName.getSelectionIndex() < 0) {
700            valid = false;
701        }
702
703        if (mTarget.getSelectionIndex() < 0 || mAbi.getSelectionIndex() < 0) {
704            valid = false;
705        }
706
707        if (mRam.getText().isEmpty()) {
708            valid = false;
709        }
710
711        if (mVmHeap.getText().isEmpty()) {
712            valid = false;
713        }
714
715        if (mDataPartition.getText().isEmpty() || mDataPartitionSize.getSelectionIndex() < 0) {
716            valid = false;
717            error = "Data partition must be a valid file size.";
718        }
719
720        // validate sdcard size or file
721        if (mSdCardSizeRadio.getSelection()) {
722            if (!mSdCardSize.getText().isEmpty() && mSdCardSizeCombo.getSelectionIndex() >= 0) {
723                try {
724                    long sdSize = Long.parseLong(mSdCardSize.getText());
725
726                    int sizeIndex = mSdCardSizeCombo.getSelectionIndex();
727                    if (sizeIndex >= 0) {
728                        // index 0 shifts by 10 (1024=K), index 1 by 20, etc.
729                        sdSize <<= 10 * (1 + sizeIndex);
730                    }
731
732                    if (sdSize < AvdManager.SDCARD_MIN_BYTE_SIZE ||
733                            sdSize > AvdManager.SDCARD_MAX_BYTE_SIZE) {
734                        valid = false;
735                        error = "SD Card size is invalid. Range is 9 MiB..1023 GiB.";
736                    }
737                } catch (NumberFormatException e) {
738                    valid = false;
739                    error = " SD Card size must be a valid integer between 9 MiB and 1023 GiB";
740                }
741            }
742        } else {
743            if (mSdCardFile.getText().isEmpty() || !new File(mSdCardFile.getText()).isFile()) {
744                valid = false;
745                error = "SD Card path isn't valid.";
746            }
747        }
748
749        if (mForceCreation.isEnabled() && !mForceCreation.getSelection()) {
750            valid = false;
751            error = String.format(
752                    "The AVD name '%s' is already used.\n" +
753                            "Check \"Override the existing AVD\" to delete the existing one.",
754                    mAvdName.getText());
755        }
756
757        if (mAvdInfo != null && !mAvdInfo.getName().equals(mAvdName.getText())) {
758            warning = String.format("The AVD '%1$s' will be duplicated into '%2$s'.",
759                    mAvdInfo.getName(),
760                    mAvdName.getText());
761        }
762
763        if (mGpuEmulation.getSelection() && mSnapshot.getSelection()) {
764            valid = false;
765            error = "GPU Emulation and Snapshot cannot be used simultaneously";
766        }
767
768        mOkButton.setEnabled(valid);
769        if (error != null) {
770            mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png")); //$NON-NLS-1$
771            mStatusLabel.setText(error);
772        } else if (warning != null) {
773            mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png")); //$NON-NLS-1$
774            mStatusLabel.setText(warning);
775        } else {
776            mStatusIcon.setImage(null);
777            mStatusLabel.setText(" \n "); //$NON-NLS-1$
778        }
779
780        mStatusComposite.pack(true);
781    }
782
783    private boolean createAvd() {
784
785        String avdName = mAvdName.getText();
786        if (avdName == null || avdName.isEmpty()) {
787            return false;
788        }
789
790        String targetName = mTarget.getItem(mTarget.getSelectionIndex());
791        IAndroidTarget target = mCurrentTargets.get(targetName);
792        if (target == null) {
793            return false;
794        }
795
796        // get the abi type
797        String abiType = SdkConstants.ABI_ARMEABI;
798        ISystemImage[] systemImages = getSystemImages(target);
799        if (systemImages.length > 0) {
800            int abiIndex = mAbi.getSelectionIndex();
801            if (abiIndex >= 0) {
802                String prettyname = mAbi.getItem(abiIndex);
803                // Extract the abi type
804                int firstIndex = prettyname.indexOf("(");
805                int lastIndex = prettyname.indexOf(")");
806                abiType = prettyname.substring(firstIndex + 1, lastIndex);
807            }
808        }
809
810        // get the SD card data from the UI.
811        String sdName = null;
812        if (mSdCardSizeRadio.getSelection()) {
813            // size mode
814            String value = mSdCardSize.getText().trim();
815            if (value.length() > 0) {
816                sdName = value;
817                // add the unit
818                switch (mSdCardSizeCombo.getSelectionIndex()) {
819                    case 0:
820                        sdName += "K"; //$NON-NLS-1$
821                        break;
822                    case 1:
823                        sdName += "M"; //$NON-NLS-1$
824                        break;
825                    case 2:
826                        sdName += "G"; //$NON-NLS-1$
827                        break;
828                    default:
829                        // shouldn't be here
830                        assert false;
831                }
832            }
833        } else {
834            // file mode.
835            sdName = mSdCardFile.getText().trim();
836        }
837
838        // Get the device
839        List<Device> devices = mDeviceMap.get(mDeviceManufacturer.getText());
840        if (devices == null) {
841            return false;
842        }
843
844        Device device = null;
845        for (Device d : devices) {
846            if (mDeviceName.getText().equals(d.getName())) {
847                device = d;
848                break;
849            }
850        }
851
852        if (device == null) {
853            return false;
854        }
855
856        Screen s = device.getDefaultHardware().getScreen();
857        String skinName = s.getXDimension() + "x" + s.getYDimension();
858
859        ILogger log = mSdkLog;
860        if (log == null || log instanceof MessageBoxLog) {
861            // If the current logger is a message box, we use our own (to make sure
862            // to display errors right away and customize the title).
863            log = new MessageBoxLog(
864                    String.format("Result of creating AVD '%s':", avdName),
865                    getContents().getDisplay(),
866                    false /* logErrorsOnly */);
867        }
868
869        Map<String, String> hwProps = DeviceManager.getHardwareProperties(device);
870        if (mGpuEmulation.getSelection()) {
871            hwProps.put(AvdManager.AVD_INI_GPU_EMULATION, HardwareProperties.BOOLEAN_YES);
872        }
873
874        File avdFolder = null;
875        try {
876            avdFolder = AvdInfo.getDefaultAvdFolder(mAvdManager, avdName);
877        } catch (AndroidLocationException e) {
878            return false;
879        }
880
881        // Although the device has this information, some devices have more RAM than we'd want to
882        // allocate to an emulator.
883        hwProps.put(AvdManager.AVD_INI_RAM_SIZE, mRam.getText());
884        hwProps.put(AvdManager.AVD_INI_VM_HEAP_SIZE, mVmHeap.getText());
885
886        String suffix;
887        switch (mDataPartitionSize.getSelectionIndex()) {
888            case 0:
889                suffix = "M";
890                break;
891            case 1:
892                suffix = "G";
893                break;
894            default:
895                suffix = "K";
896        }
897        hwProps.put(AvdManager.AVD_INI_DATA_PARTITION_SIZE, mDataPartition.getText()+suffix);
898
899        if (mFrontCamera.isEnabled()) {
900            hwProps.put(AvdManager.AVD_INI_CAMERA_FRONT,
901                    mFrontCamera.getText().toLowerCase());
902        }
903
904        if (mBackCamera.isEnabled()) {
905            hwProps.put(AvdManager.AVD_INI_CAMERA_BACK,
906                    mBackCamera.getText().toLowerCase());
907        }
908
909        if (sdName != null) {
910            hwProps.put(HardwareProperties.HW_SDCARD, HardwareProperties.BOOLEAN_YES);
911        }
912
913        AvdInfo avdInfo = mAvdManager.createAvd(avdFolder,
914                avdName,
915                target,
916                abiType,
917                skinName,
918                sdName,
919                hwProps,
920                mSnapshot.getSelection(),
921                mForceCreation.getSelection(),
922                mAvdInfo != null, // edit existing
923                log);
924
925        boolean success = avdInfo != null;
926
927        if (log instanceof MessageBoxLog) {
928            ((MessageBoxLog) log).displayResult(success);
929        }
930        return success;
931    }
932
933    private void fillExistingAvdInfo(AvdInfo avd) {
934        mAvdName.setText(avd.getName());
935        String manufacturer = avd.getDeviceManufacturer();
936        for (int i = 0; i < mDeviceManufacturer.getItemCount(); i++) {
937            if (mDeviceManufacturer.getItem(i).equals(manufacturer)) {
938                mDeviceManufacturer.select(i);
939                break;
940            }
941        }
942        reloadDeviceNameCombo();
943
944        String deviceName = avd.getDeviceName();
945        for (int i = 0; i < mDeviceName.getItemCount(); i++) {
946            if (mDeviceName.getItem(i).equals(deviceName)) {
947                mDeviceName.select(i);
948                break;
949            }
950        }
951        toggleCameras();
952
953        IAndroidTarget target = avd.getTarget();
954
955        if (target != null && !mCurrentTargets.isEmpty()) {
956            // Try to select the target in the target combo.
957            // This will fail if the AVD needs to be repaired.
958            //
959            // This is a linear search but the list is always
960            // small enough and we only do this once.
961            int n = mTarget.getItemCount();
962            for (int i = 0; i < n; i++) {
963                if (target.equals(mCurrentTargets.get(mTarget.getItem(i)))) {
964                    mTarget.select(i);
965                    reloadAbiTypeCombo();
966                    break;
967                }
968            }
969        }
970
971        ISystemImage[] systemImages = getSystemImages(target);
972        if (target != null && systemImages.length > 0) {
973            mAbi.setEnabled(systemImages.length > 1);
974            String abiType = AvdInfo.getPrettyAbiType(avd.getAbiType());
975            int n = mAbi.getItemCount();
976            for (int i = 0; i < n; i++) {
977                if (abiType.equals(mAbi.getItem(i))) {
978                    mAbi.select(i);
979                    break;
980                }
981            }
982        }
983
984        Map<String, String> props = avd.getProperties();
985
986        if (props != null) {
987            String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
988            if (snapshots != null && snapshots.length() > 0) {
989                mSnapshot.setSelection(snapshots.equals("true"));
990            }
991
992            String gpuEmulation = props.get(AvdManager.AVD_INI_GPU_EMULATION);
993            mGpuEmulation.setSelection(gpuEmulation != null &&
994                    gpuEmulation.equals(HardwareProperties.BOOLEAN_VALUES[0]));
995
996            String sdcard = props.get(AvdManager.AVD_INI_SDCARD_PATH);
997            if (sdcard != null && sdcard.length() > 0) {
998                enableSdCardWidgets(false);
999                mSdCardSizeRadio.setSelection(false);
1000                mSdCardFileRadio.setSelection(true);
1001                mSdCardFile.setText(sdcard);
1002            }
1003
1004            String ramSize = props.get(AvdManager.AVD_INI_RAM_SIZE);
1005            if (ramSize != null) {
1006                mRam.setText(ramSize);
1007            }
1008
1009            String vmHeapSize = props.get(AvdManager.AVD_INI_VM_HEAP_SIZE);
1010            if (vmHeapSize != null) {
1011                mVmHeap.setText(vmHeapSize);
1012            }
1013
1014            String dataPartitionSize = props.get(AvdManager.AVD_INI_DATA_PARTITION_SIZE);
1015            if (dataPartitionSize != null) {
1016                mDataPartition.setText(
1017                        dataPartitionSize.substring(0, dataPartitionSize.length() - 1));
1018                switch (dataPartitionSize.charAt(dataPartitionSize.length() - 1)) {
1019                    case 'M':
1020                        mDataPartitionSize.select(0);
1021                        break;
1022                    case 'G':
1023                        mDataPartitionSize.select(1);
1024                        break;
1025                    default:
1026                        mDataPartitionSize.select(-1);
1027                }
1028            }
1029
1030            String cameraFront = props.get(AvdManager.AVD_INI_CAMERA_FRONT);
1031            if (cameraFront != null) {
1032                String[] items = mFrontCamera.getItems();
1033                for (int i = 0; i < items.length; i++) {
1034                    if (items[i].toLowerCase().equals(cameraFront)) {
1035                        mFrontCamera.select(i);
1036                        break;
1037                    }
1038                }
1039            }
1040
1041            String cameraBack = props.get(AvdManager.AVD_INI_CAMERA_BACK);
1042            if (cameraBack != null) {
1043                String[] items = mBackCamera.getItems();
1044                for (int i = 0; i < items.length; i++) {
1045                    if (items[i].toLowerCase().equals(cameraBack)) {
1046                        mBackCamera.select(i);
1047                        break;
1048                    }
1049                }
1050            }
1051
1052            sdcard = props.get(AvdManager.AVD_INI_SDCARD_SIZE);
1053            if (sdcard != null && sdcard.length() > 0) {
1054                String[] values = new String[2];
1055                long sdcardSize = AvdManager.parseSdcardSize(sdcard, values);
1056
1057                if (sdcardSize != AvdManager.SDCARD_NOT_SIZE_PATTERN) {
1058                    enableSdCardWidgets(true);
1059                    mSdCardFileRadio.setSelection(false);
1060                    mSdCardSizeRadio.setSelection(true);
1061
1062                    mSdCardSize.setText(values[0]);
1063
1064                    String suffix = values[1];
1065                    int n = mSdCardSizeCombo.getItemCount();
1066                    for (int i = 0; i < n; i++) {
1067                        if (mSdCardSizeCombo.getItem(i).startsWith(suffix)) {
1068                            mSdCardSizeCombo.select(i);
1069                        }
1070                    }
1071                }
1072            }
1073        }
1074    }
1075
1076    /**
1077     * Returns the list of system images of a target.
1078     * <p/>
1079     * If target is null, returns an empty list. If target is an add-on with no
1080     * system images, return the list from its parent platform.
1081     *
1082     * @param target An IAndroidTarget. Can be null.
1083     * @return A non-null ISystemImage array. Can be empty.
1084     */
1085    private ISystemImage[] getSystemImages(IAndroidTarget target) {
1086        if (target != null) {
1087            ISystemImage[] images = target.getSystemImages();
1088
1089            if ((images == null || images.length == 0) && !target.isPlatform()) {
1090                // If an add-on does not provide any system images, use the ones
1091                // from the parent.
1092                images = target.getParent().getSystemImages();
1093            }
1094
1095            if (images != null) {
1096                return images;
1097            }
1098        }
1099
1100        return new ISystemImage[0];
1101    }
1102
1103}
1104