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.annotations.Nullable;
20import com.android.resources.Density;
21import com.android.resources.Keyboard;
22import com.android.resources.KeyboardState;
23import com.android.resources.Navigation;
24import com.android.resources.NavigationState;
25import com.android.resources.ResourceEnum;
26import com.android.resources.ScreenOrientation;
27import com.android.resources.ScreenRatio;
28import com.android.resources.ScreenSize;
29import com.android.resources.TouchScreen;
30import com.android.sdklib.devices.Abi;
31import com.android.sdklib.devices.ButtonType;
32import com.android.sdklib.devices.Camera;
33import com.android.sdklib.devices.CameraLocation;
34import com.android.sdklib.devices.Device;
35import com.android.sdklib.devices.DeviceManager;
36import com.android.sdklib.devices.Hardware;
37import com.android.sdklib.devices.Multitouch;
38import com.android.sdklib.devices.Network;
39import com.android.sdklib.devices.PowerType;
40import com.android.sdklib.devices.Screen;
41import com.android.sdklib.devices.ScreenType;
42import com.android.sdklib.devices.Sensor;
43import com.android.sdklib.devices.Software;
44import com.android.sdklib.devices.State;
45import com.android.sdklib.devices.Storage;
46import com.android.sdkuilib.internal.repository.icons.ImageFactory;
47import com.android.sdkuilib.ui.GridDialog;
48
49import org.eclipse.jface.dialogs.IDialogConstants;
50import org.eclipse.swt.SWT;
51import org.eclipse.swt.events.ModifyEvent;
52import org.eclipse.swt.events.ModifyListener;
53import org.eclipse.swt.events.SelectionAdapter;
54import org.eclipse.swt.events.SelectionEvent;
55import org.eclipse.swt.events.SelectionListener;
56import org.eclipse.swt.layout.GridData;
57import org.eclipse.swt.layout.GridLayout;
58import org.eclipse.swt.widgets.Button;
59import org.eclipse.swt.widgets.Combo;
60import org.eclipse.swt.widgets.Composite;
61import org.eclipse.swt.widgets.Control;
62import org.eclipse.swt.widgets.Group;
63import org.eclipse.swt.widgets.Label;
64import org.eclipse.swt.widgets.Shell;
65import org.eclipse.swt.widgets.Text;
66
67import java.util.List;
68
69public class DeviceCreationDialog extends GridDialog {
70
71    private static final String MANUFACTURER = "User";
72
73    private final ImageFactory mImageFactory;
74    private final DeviceManager mManager;
75    private List<Device> mUserDevices;
76
77    private Device mDevice;
78
79    private Text mDeviceName;
80    private Text mDiagonalLength;
81    private Text mXDimension;
82    private Text mYDimension;
83    private Button mKeyboard;
84    private Button mDpad;
85    private Button mTrackball;
86    private Button mNoNav;
87    private Text mRam;
88    private Combo mRamCombo;
89    private Combo mButtons;
90    private Combo mSize;
91    private Combo mDensity;
92    private Combo mRatio;
93    private Button mAccelerometer; // hw.accelerometer
94    private Button mGyro; // hw.sensors.orientation
95    private Button mGps; // hw.sensors.gps
96    private Button mProximitySensor; // hw.sensors.proximity
97    private Button mCameraFront;
98    private Button mCameraRear;
99    private Group mStateGroup;
100    private Button mPortrait;
101    private Label mPortraitLabel;
102    private Button mPortraitNav;
103    private Button mLandscape;
104    private Label mLandscapeLabel;
105    private Button mLandscapeNav;
106    private Button mPortraitKeys;
107    private Label mPortraitKeysLabel;
108    private Button mPortraitKeysNav;
109    private Button mLandscapeKeys;
110    private Label mLandscapeKeysLabel;
111    private Button mLandscapeKeysNav;
112
113    private Button mForceCreation;
114    private Label mStatusIcon;
115    private Label mStatusLabel;
116
117    private Button mOkButton;
118
119    // The hardware instance attached to each of the states of the created
120    // device
121    private Hardware mHardware;
122    // This contains the Software for the device. Since it has no effect on the
123    // emulator whatsoever, we just use a single instance with reasonable
124    // defaults.
125    private static final Software mSoftware;
126
127    static {
128        mSoftware = new Software();
129        mSoftware.setLiveWallpaperSupport(true);
130        mSoftware.setGlVersion("2.0");
131    }
132
133    public DeviceCreationDialog(Shell parentShell,
134            DeviceManager manager,
135            ImageFactory imageFactory,
136            @Nullable Device device) {
137        super(parentShell, 2, false);
138        mImageFactory = imageFactory;
139        mDevice = device;
140        mManager = manager;
141        mUserDevices = mManager.getUserDevices();
142    }
143
144    @Override
145    protected Control createContents(Composite parent) {
146        Control control = super.createContents(parent);
147
148        mOkButton = getButton(IDialogConstants.OK_ID);
149
150        if (mDevice == null) {
151            getShell().setText("Create New Device");
152        } else {
153            if (mUserDevices.contains(mDevice)) {
154                getShell().setText("Edit Device");
155            } else {
156                getShell().setText("Clone Device");
157            }
158        }
159
160        validatePage();
161
162        return control;
163    }
164
165    @Override
166    public void createDialogContent(Composite parent) {
167
168        ValidationListener validator = new ValidationListener();
169        SizeListener sizeListener = new SizeListener();
170        NavStateListener navListener = new NavStateListener();
171
172        String tooltip = "Name of the new device";
173        generateLabel("Name:", tooltip, parent);
174        mDeviceName = generateText(parent, tooltip, new CreateNameModifyListener());
175
176        tooltip = "Diagonal length of the screen in inches";
177        generateLabel("Screen Size (in):", tooltip, parent);
178        mDiagonalLength = generateText(parent, tooltip, sizeListener);
179
180        tooltip = "The resolution of the device in pixels";
181        generateLabel("Resolution:", tooltip, parent);
182        Group dimensionGroup = new Group(parent, SWT.NONE);
183        dimensionGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
184        dimensionGroup.setLayout(new GridLayout(3, false));
185        mXDimension = generateText(dimensionGroup, tooltip, sizeListener);
186        new Label(dimensionGroup, SWT.NONE).setText("x");
187        mYDimension = generateText(dimensionGroup, tooltip, sizeListener);
188
189        tooltip = "The screen size bucket that the device falls into";
190        generateLabel("Size:", tooltip, parent);
191        mSize = generateCombo(parent, tooltip, ScreenSize.values(), 1, validator);
192
193        tooltip = "The aspect ratio bucket the screen falls into. A \"long\" screen is wider.";
194        generateLabel("Screen Ratio:", tooltip, parent);
195        mRatio = generateCombo(parent, tooltip, ScreenRatio.values(), 1, validator);
196
197        tooltip = "The pixel density bucket the device falls in";
198        generateLabel("Density:", tooltip, parent);
199        mDensity = generateCombo(parent, tooltip, Density.values(), 3, validator);
200
201        generateLabel("Sensors:", "The sensors available on the device", parent);
202        Group sensorGroup = new Group(parent, SWT.NONE);
203        sensorGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
204        sensorGroup.setLayout(new GridLayout(2, false));
205        mAccelerometer = generateButton(sensorGroup, "Accelerometer",
206                "Presence of an accelerometer", SWT.CHECK, true, validator);
207        mGyro = generateButton(sensorGroup, "Gyroscope",
208                "Presence of a gyroscope", SWT.CHECK, true, validator);
209        mGps = generateButton(sensorGroup, "GPS", "Presence of a GPS", SWT.CHECK, true, validator);
210        mProximitySensor = generateButton(sensorGroup, "Proximity Sensor",
211                "Presence of a proximity sensor", SWT.CHECK, true, validator);
212
213        generateLabel("Cameras", "The cameras available on the device", parent);
214        Group cameraGroup = new Group(parent, SWT.NONE);
215        cameraGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
216        cameraGroup.setLayout(new GridLayout(2, false));
217        mCameraFront = generateButton(cameraGroup, "Front", "Presence of a front camera",
218                SWT.CHECK, false, validator);
219        mCameraRear = generateButton(cameraGroup, "Rear", "Presence of a rear camera",
220                SWT.CHECK, true, validator);
221
222        generateLabel("Input:", "The input hardware on the given device", parent);
223        Group inputGroup = new Group(parent, SWT.NONE);
224        inputGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
225        inputGroup.setLayout(new GridLayout(3, false));
226        mKeyboard = generateButton(inputGroup, "Keyboard", "Presence of a hardware keyboard",
227                SWT.CHECK, false,
228                new KeyboardListener());
229        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
230        gridData.horizontalSpan = 3;
231        mKeyboard.setLayoutData(gridData);
232        mNoNav = generateButton(inputGroup, "No Nav", "No hardware navigation",
233                SWT.RADIO, true, navListener);
234        mDpad = generateButton(inputGroup, "DPad", "The device has a DPad navigation element",
235                SWT.RADIO, false, navListener);
236        mTrackball = generateButton(inputGroup, "Trackball",
237                "The device has a trackball navigation element", SWT.RADIO, false, navListener);
238
239        tooltip = "The amount of RAM on the device";
240        generateLabel("RAM:", tooltip, parent);
241        Group ramGroup = new Group(parent, SWT.NONE);
242        ramGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
243        ramGroup.setLayout(new GridLayout(2, false));
244        mRam = generateText(ramGroup, tooltip, validator);
245        mRamCombo = new Combo(ramGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
246        mRamCombo.setToolTipText(tooltip);
247        mRamCombo.add("MiB");
248        mRamCombo.add("GiB");
249        mRamCombo.select(0);
250        mRamCombo.addModifyListener(validator);
251
252        tooltip = "Type of buttons (Home, Menu, etc.) on the device. "
253                + "This can be software buttons like on the Galaxy Nexus, or hardware buttons like "
254                + "the capacitive buttons on the Nexus S.";
255        generateLabel("Buttons:", tooltip, parent);
256        mButtons = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
257        mButtons.setToolTipText(tooltip);
258        mButtons.add("Software");
259        mButtons.add("Hardware");
260        mButtons.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
261        mButtons.select(0);
262        mButtons.addModifyListener(validator);
263
264        generateLabel("Device States:", "The available states for the given device", parent);
265
266        mStateGroup = new Group(parent, SWT.NONE);
267        mStateGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
268        mStateGroup.setLayout(new GridLayout(2, true));
269
270        tooltip = "The device has a portait position with no keyboard available";
271        mPortraitLabel = generateLabel("Portrait:", tooltip, mStateGroup);
272        gridData = new GridData(GridData.FILL_HORIZONTAL);
273        gridData.horizontalSpan = 2;
274        mPortraitLabel.setLayoutData(gridData);
275        mPortrait = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
276                navListener);
277        mPortraitNav = generateButton(mStateGroup, "Navigation",
278                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
279        mPortraitNav.setEnabled(false);
280
281        tooltip = "The device has a landscape position with no keyboard available";
282        mLandscapeLabel = generateLabel("Landscape:", tooltip, mStateGroup);
283        gridData = new GridData(GridData.FILL_HORIZONTAL);
284        gridData.horizontalSpan = 2;
285        mLandscapeLabel.setLayoutData(gridData);
286        mLandscape = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
287                navListener);
288        mLandscapeNav = generateButton(mStateGroup, "Navigation",
289                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
290        mLandscapeNav.setEnabled(false);
291
292        tooltip = "The device has a portait position with a keyboard available";
293        mPortraitKeysLabel = generateLabel("Portrait with keyboard:", tooltip, mStateGroup);
294        gridData = new GridData(GridData.FILL_HORIZONTAL);
295        gridData.horizontalSpan = 2;
296        mPortraitKeysLabel.setLayoutData(gridData);
297        mPortraitKeysLabel.setEnabled(false);
298        mPortraitKeys = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
299                navListener);
300        mPortraitKeys.setEnabled(false);
301        mPortraitKeysNav = generateButton(mStateGroup, "Navigation",
302                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
303        mPortraitKeysNav.setEnabled(false);
304
305        tooltip = "The device has a landscape position with the keyboard open";
306        mLandscapeKeysLabel = generateLabel("Landscape with keyboard:", tooltip, mStateGroup);
307        gridData = new GridData(GridData.FILL_HORIZONTAL);
308        gridData.horizontalSpan = 2;
309        mLandscapeKeysLabel.setLayoutData(gridData);
310        mLandscapeKeysLabel.setEnabled(false);
311        mLandscapeKeys = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
312                navListener);
313        mLandscapeKeys.setEnabled(false);
314        mLandscapeKeysNav = generateButton(mStateGroup, "Navigation",
315                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
316        mLandscapeKeysNav.setEnabled(false);
317
318        mForceCreation = new Button(parent, SWT.CHECK);
319        mForceCreation.setText("Override the existing device with the same name");
320        mForceCreation
321                .setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD.");
322        mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER,
323                true, false, 2, 1));
324        mForceCreation.setEnabled(false);
325        mForceCreation.addSelectionListener(validator);
326
327        // add a separator to separate from the ok/cancel button
328        Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
329        label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
330
331        // add stuff for the error display
332        Composite statusComposite = new Composite(parent, SWT.NONE);
333        GridLayout gl;
334        statusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
335                true, false, 3, 1));
336        statusComposite.setLayout(gl = new GridLayout(2, false));
337        gl.marginHeight = gl.marginWidth = 0;
338
339        mStatusIcon = new Label(statusComposite, SWT.NONE);
340        mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
341                false, false));
342        mStatusLabel = new Label(statusComposite, SWT.NONE);
343        mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
344        mStatusLabel.setText(""); //$NON-NLS-1$
345
346        prefillWithDevice(mDevice);
347
348        validatePage();
349    }
350
351    private Button generateButton(Composite parent, String text, String tooltip, int type,
352            boolean selected, SelectionListener listener) {
353        Button b = new Button(parent, type);
354        b.setText(text);
355        b.setToolTipText(tooltip);
356        b.setSelection(selected);
357        b.addSelectionListener(listener);
358        b.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
359        return b;
360    }
361
362    /**
363     * Generates a combo widget attached to the given parent, then sets the
364     * tooltip, adds all of the {@link String}s returned by
365     * {@link ResourceEnum#getResourceValue()} for each {@link ResourceEnum},
366     * sets the combo to the given index and adds the given
367     * {@link ModifyListener}.
368     */
369    private Combo generateCombo(Composite parent, String tooltip, ResourceEnum[] values,
370            int selection,
371            ModifyListener validator) {
372        Combo c = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
373        c.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
374        c.setToolTipText(tooltip);
375        for (ResourceEnum r : values) {
376            c.add(r.getResourceValue());
377        }
378        c.select(selection);
379        c.addModifyListener(validator);
380        return c;
381    }
382
383    /** Generates a text widget with the given tooltip, parent and listener */
384    private Text generateText(Composite parent, String tooltip, ModifyListener listener) {
385        Text t = new Text(parent, SWT.BORDER);
386        t.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
387        t.setToolTipText(tooltip);
388        t.addModifyListener(listener);
389        return t;
390    }
391
392    /** Generates a label and attaches it to the given parent */
393    private Label generateLabel(String text, String tooltip, Composite parent) {
394        Label label = new Label(parent, SWT.NONE);
395        label.setText(text);
396        label.setToolTipText(tooltip);
397        label.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER));
398        return label;
399    }
400
401    /**
402     * Callback when the device name is changed. Enforces that device names
403     * don't conflict with already existing devices unless we're editing that
404     * device.
405     */
406    private class CreateNameModifyListener implements ModifyListener {
407        @Override
408        public void modifyText(ModifyEvent e) {
409            String name = mDeviceName.getText();
410            boolean nameCollision = false;
411            for (Device d : mUserDevices) {
412                if (MANUFACTURER.equals(d.getManufacturer()) && name.equals(d.getName())) {
413                    nameCollision = true;
414                    break;
415                }
416            }
417            mForceCreation.setEnabled(nameCollision);
418            mForceCreation.setSelection(!nameCollision);
419
420            validatePage();
421        }
422    }
423
424    /**
425     * Callback attached to the diagonal length and resolution text boxes. Sets
426     * the screen size and display density based on their values, then validates
427     * the page.
428     */
429    private class SizeListener implements ModifyListener {
430        @Override
431        public void modifyText(ModifyEvent e) {
432
433            if (!mDiagonalLength.getText().isEmpty()) {
434                double diagonal = Double.parseDouble(mDiagonalLength.getText());
435                double diagonalDp = 160.0 * diagonal;
436
437                // Set the Screen Size
438                if (diagonalDp >= 1200) {
439                    mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("xlarge")));
440                } else if (diagonalDp >= 800) {
441                    mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("large")));
442                } else if (diagonalDp >= 568) {
443                    mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("normal")));
444                } else {
445                    mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("small")));
446                }
447                if (!mXDimension.getText().isEmpty() && !mYDimension.getText().isEmpty()) {
448
449                    // Set the density based on which bucket it's closest to
450                    double x = Double.parseDouble(mXDimension.getText());
451                    double y = Double.parseDouble(mYDimension.getText());
452                    double dpi = Math.sqrt(x * x + y * y) / diagonal;
453                    double difference = Double.MAX_VALUE;
454                    Density bucket = Density.MEDIUM;
455                    for (Density d : Density.values()) {
456                        if (Math.abs(d.getDpiValue() - dpi) < difference) {
457                            difference = Math.abs(d.getDpiValue() - dpi);
458                            bucket = d;
459                        }
460                    }
461                    mDensity.select(Density.getIndex(bucket));
462                }
463            }
464        }
465    }
466
467
468    /**
469     * Callback attached to the keyboard checkbox.Enables / disables device
470     * states based on the keyboard presence and then validates the page.
471     */
472    private class KeyboardListener extends SelectionAdapter {
473        @Override
474        public void widgetSelected(SelectionEvent event) {
475            super.widgetSelected(event);
476            if (mKeyboard.getSelection()) {
477                mPortraitKeys.setEnabled(true);
478                mPortraitKeysLabel.setEnabled(true);
479                mLandscapeKeys.setEnabled(true);
480                mLandscapeKeysLabel.setEnabled(true);
481            } else {
482                mPortraitKeys.setEnabled(false);
483                mPortraitKeysLabel.setEnabled(false);
484                mLandscapeKeys.setEnabled(false);
485                mLandscapeKeysLabel.setEnabled(false);
486            }
487            toggleNav();
488            validatePage();
489        }
490
491    }
492
493    /**
494     * Listens for changes on widgets that affect nav availability and toggles
495     * the nav checkboxes for device states based on them.
496     */
497    private class NavStateListener extends SelectionAdapter {
498        @Override
499        public void widgetSelected(SelectionEvent event) {
500            super.widgetSelected(event);
501            toggleNav();
502            validatePage();
503        }
504    }
505
506    /**
507     * Method that inspects all of the relevant dialog state and enables or disables the nav
508     * elements accordingly.
509     */
510    private void toggleNav() {
511        mPortraitNav.setEnabled(mPortrait.getSelection() && !mNoNav.getSelection());
512        mLandscapeNav.setEnabled(mLandscape.getSelection() && !mNoNav.getSelection());
513        mPortraitKeysNav.setEnabled(mPortraitKeys.getSelection() && mPortraitKeys.getEnabled()
514                && !mNoNav.getSelection());
515        mLandscapeKeysNav.setEnabled(mLandscapeKeys.getSelection()
516                && mLandscapeKeys.getEnabled() && !mNoNav.getSelection());
517        validatePage();
518    }
519
520    /**
521     * Callback that validates the page on modification events or widget
522     * selections
523     */
524    private class ValidationListener extends SelectionAdapter implements ModifyListener {
525        @Override
526        public void modifyText(ModifyEvent e) {
527            validatePage();
528        }
529
530        @Override
531        public void widgetSelected(SelectionEvent e) {
532            super.widgetSelected(e);
533            validatePage();
534        }
535    }
536
537    /**
538     * Validates all of the config options to ensure a valid device can be
539     * created from them.
540     *
541     * @return Whether the config options will result in a valid device.
542     */
543    private boolean validatePage() {
544        boolean valid = true;
545        String error = null;
546        String warning = null;
547        setError(null);
548
549        String name = mDeviceName.getText();
550
551        /* If we're editing / cloning a device, this will get called when the name gets pre-filled
552         * but the ok button won't be populated yet, so we need to skip the initial setting.
553         */
554        if (mOkButton != null) {
555            if (mDevice == null) {
556                getShell().setText("Create New Device");
557                mOkButton.setText("Create Device");
558            } else {
559                if (mDevice.getName().equals(name)){
560                    if (mUserDevices.contains(mDevice)) {
561                        getShell().setText("Edit Device");
562                        mOkButton.setText("Edit Device");
563                    } else {
564                        warning = "Only user created devices are editable.\nA clone of it will be created under " +
565                        "the \"User\" category.";
566                        getShell().setText("Clone Device");
567                        mOkButton.setText("Clone Device");
568                    }
569                } else {
570                    warning = "The device \"" + mDevice.getName() +"\" will be duplicated into\n" +
571                            "\"" + name + "\" under the \"User\" category";
572                    getShell().setText("Clone Device");
573                    mOkButton.setText("Clone Device");
574                }
575            }
576        }
577
578        if (name.isEmpty()) {
579            valid = false;
580        }
581        if (!validateFloat("Diagonal Length", mDiagonalLength.getText())) {
582            valid = false;
583        }
584        if (!validateInt("Resolution", mXDimension.getText())) {
585            valid = false;
586        }
587        if (!validateInt("Resolution", mYDimension.getText())) {
588            valid = false;
589        }
590        if (mSize.getSelectionIndex() < 0) {
591            error = "A size bucket must be selected.";
592            valid = false;
593        }
594        if (mDensity.getSelectionIndex() < 0) {
595            error = "A screen density bucket must be selected";
596            valid = false;
597        }
598        if (mRatio.getSelectionIndex() < 0) {
599            error = "A screen ratio must be selected.";
600            valid = false;
601        }
602        if (!mNoNav.getSelection() && !mTrackball.getSelection() && !mDpad.getSelection()) {
603            error = "A mode of hardware navigation, or no navigation, has to be selected.";
604            valid = false;
605        }
606        if (!validateInt("RAM", mRam.getText())) {
607            valid = false;
608        }
609        if (mRamCombo.getSelectionIndex() < 0) {
610            error = "RAM must have a selected unit.";
611            valid = false;
612        }
613        if (mButtons.getSelectionIndex() < 0) {
614            error = "A button type must be selected.";
615            valid = false;
616        }
617        if (mKeyboard.getSelection()) {
618            if (!mPortraitKeys.getSelection()
619                    && !mPortrait.getSelection()
620                    && !mLandscapeKeys.getSelection()
621                    && !mLandscape.getSelection()) {
622                error = "At least one device state must be enabled.";
623                valid = false;
624            }
625        } else {
626            if (!mPortrait.getSelection() && !mLandscape.getSelection()) {
627                error = "At least one device state must be enabled";
628                valid = false;
629            }
630        }
631        if (mForceCreation.isEnabled() && !mForceCreation.getSelection()) {
632            error = "Name conflicts with an existing device.";
633            valid = false;
634        }
635
636        if (mOkButton != null) {
637            mOkButton.setEnabled(valid);
638        }
639
640        if (error != null) {
641            setError(error);
642        } else if (warning != null) {
643            setWarning(warning);
644        }
645
646        return valid;
647    }
648
649    /**
650     * Validates the string is a valid, positive float. If not, it sets the
651     * error at the bottom of the dialog and returns false. Note this does
652     * <b>not</b> unset the error message, it's up to the caller to unset it iff
653     * it knows there are no errors on the page.
654     */
655    private boolean validateFloat(String box, String value) {
656        if (value == null || value.isEmpty()) {
657            return false;
658        }
659        boolean ret = true;
660        try {
661            double val = Double.parseDouble(value);
662            if (val <= 0) {
663                ret = false;
664            }
665        } catch (NumberFormatException e) {
666            ret = false;
667        }
668        if (!ret) {
669            setError(box + " must be a valid, positive number.");
670        }
671        return ret;
672    }
673
674    /**
675     * Validates the string is a valid, positive integer. If not, it sets the
676     * error at the bottom of the dialog and returns false. Note this does
677     * <b>not</b> unset the error message, it's up to the caller to unset it iff
678     * it knows there are no errors on the page.
679     */
680    private boolean validateInt(String box, String value) {
681        if (value == null || value.isEmpty()) {
682            return false;
683        }
684        boolean ret = true;
685        try {
686            int val = Integer.parseInt(value);
687            if (val <= 0) {
688                ret = false;
689            }
690        } catch (NumberFormatException e) {
691            ret = false;
692        }
693
694        if (!ret) {
695            setError(box + " must be a valid, positive integer.");
696        }
697
698        return ret;
699    }
700
701    /**
702     * Sets the error to the given string. If null, removes the error message.
703     */
704    private void setError(@Nullable String error) {
705        if (error == null) {
706            mStatusIcon.setImage(null);
707            mStatusLabel.setText("");
708        } else {
709            mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png"));
710            mStatusLabel.setText(error);
711        }
712    }
713
714    /**
715     * Sets the warning message to the given string. If null, removes the
716     * warning message.
717     */
718    private void setWarning(@Nullable String warning) {
719        if (warning == null) {
720            mStatusIcon.setImage(null);
721            mStatusLabel.setText("");
722        } else {
723            mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png"));
724            mStatusLabel.setText(warning);
725        }
726    }
727
728    /** Sets the hardware for the new device */
729    private void prefillWithDevice(@Nullable Device device) {
730        if (device == null) {
731
732            // Setup the default hardware instance with reasonable values for
733            // the things which are configurable via this dialog.
734            mHardware = new Hardware();
735
736            Screen s = new Screen();
737            s.setXdpi(316);
738            s.setYdpi(316);
739            s.setMultitouch(Multitouch.JAZZ_HANDS);
740            s.setMechanism(TouchScreen.FINGER);
741            s.setScreenType(ScreenType.CAPACITIVE);
742            mHardware.setScreen(s);
743
744            mHardware.addNetwork(Network.BLUETOOTH);
745            mHardware.addNetwork(Network.WIFI);
746            mHardware.addNetwork(Network.NFC);
747
748            mHardware.addSensor(Sensor.BAROMETER);
749            mHardware.addSensor(Sensor.COMPASS);
750            mHardware.addSensor(Sensor.LIGHT_SENSOR);
751
752            mHardware.setHasMic(true);
753            mHardware.addInternalStorage(new Storage(4, Storage.Unit.GiB));
754            mHardware.setCpu("Generic CPU");
755            mHardware.setGpu("Generic GPU");
756
757            mHardware.addSupportedAbi(Abi.ARMEABI);
758            mHardware.addSupportedAbi(Abi.ARMEABI_V7A);
759            mHardware.addSupportedAbi(Abi.MIPS);
760            mHardware.addSupportedAbi(Abi.X86);
761
762            mHardware.setChargeType(PowerType.BATTERY);
763            return;
764        }
765        mHardware = device.getDefaultHardware().deepCopy();
766        mDeviceName.setText(device.getName());
767        mForceCreation.setSelection(true);
768        Screen s = mHardware.getScreen();
769        mDiagonalLength.setText(Double.toString(s.getDiagonalLength()));
770        mXDimension.setText(Integer.toString(s.getXDimension()));
771        mYDimension.setText(Integer.toString(s.getYDimension()));
772        String size = s.getSize().getResourceValue();
773        for (int i = 0; i < mSize.getItemCount(); i++) {
774            if (size.equals(mSize.getItem(i))) {
775                mSize.select(i);
776                break;
777            }
778        }
779        String ratio = s.getRatio().getResourceValue();
780        for (int i = 0; i < mRatio.getItemCount(); i++) {
781            if (ratio.equals(mRatio.getItem(i))) {
782                mRatio.select(i);
783                break;
784            }
785        }
786        String density = s.getPixelDensity().getResourceValue();
787        for (int i = 0; i < mDensity.getItemCount(); i++) {
788            if (density.equals(mDensity.getItem(i))) {
789                mDensity.select(i);
790                break;
791            }
792        }
793        mKeyboard.setSelection(!Keyboard.NOKEY.equals(mHardware.getKeyboard()));
794        mDpad.setSelection(Navigation.DPAD.equals(mHardware.getNav()));
795        mTrackball.setSelection(Navigation.TRACKBALL.equals(mHardware.getNav()));
796        mNoNav.setSelection(Navigation.NONAV.equals(mHardware.getNav()));
797        mAccelerometer.setSelection(mHardware.getSensors().contains(Sensor.ACCELEROMETER));
798        mGyro.setSelection(mHardware.getSensors().contains(Sensor.GYROSCOPE));
799        mGps.setSelection(mHardware.getSensors().contains(Sensor.GPS));
800        mProximitySensor.setSelection(mHardware.getSensors().contains(Sensor.PROXIMITY_SENSOR));
801        mCameraFront.setSelection(false);
802        mCameraRear.setSelection(false);
803        for (Camera c : mHardware.getCameras()) {
804            if (CameraLocation.FRONT.equals(c.getLocation())) {
805                mCameraFront.setSelection(true);
806            } else if (CameraLocation.BACK.equals(c.getLocation())) {
807                mCameraRear.setSelection(true);
808            }
809        }
810        mRam.setText(Long.toString(mHardware.getRam().getSizeAsUnit(Storage.Unit.MiB)));
811        mRamCombo.select(0);
812        for (int i = 0; i < mButtons.getItemCount(); i++) {
813            if (mButtons.getItem(i).equals(mHardware.getButtonType().toString())) {
814                mButtons.select(i);
815                break;
816            }
817        }
818
819        for (State state : device.getAllStates()) {
820            Button nav = null;
821            if (state.getOrientation().equals(ScreenOrientation.PORTRAIT)) {
822                if (state.getKeyState().equals(KeyboardState.EXPOSED)) {
823                    mPortraitKeys.setSelection(true);
824                    nav = mPortraitKeysNav;
825                } else {
826                    mPortrait.setSelection(true);
827                    nav = mPortraitNav;
828                }
829            } else {
830                if (state.getKeyState().equals(KeyboardState.EXPOSED)) {
831                    mLandscapeKeys.setSelection(true);
832                    nav = mLandscapeKeysNav;
833                } else {
834                    mLandscape.setSelection(true);
835                    nav = mLandscapeNav;
836                }
837            }
838            nav.setSelection(state.getNavState().equals(NavigationState.EXPOSED)
839                    && !mHardware.getNav().equals(Navigation.NONAV));
840        }
841    }
842
843    /**
844     * If given a valid page, generates the corresponding device. The device is
845     * then added to the user device list, replacing any previous device with
846     * its given name and manufacturer, and the list is saved out to disk.
847     */
848    @Override
849    protected void okPressed() {
850        if (validatePage()) {
851            Device.Builder builder = new Device.Builder();
852            builder.setManufacturer("User");
853            builder.setName(mDeviceName.getText());
854            builder.addSoftware(mSoftware);
855            Screen s = mHardware.getScreen();
856            double diagonal = Double.parseDouble(mDiagonalLength.getText());
857            int x = Integer.parseInt(mXDimension.getText());
858            int y = Integer.parseInt(mYDimension.getText());
859            s.setDiagonalLength(diagonal);
860            s.setXDimension(x);
861            s.setYDimension(y);
862            // The diagonal DPI will be somewhere in between the X and Y dpi if
863            // they differ
864            double dpi = Math.sqrt(x * x + y * y) / diagonal;
865            s.setXdpi(dpi);
866            s.setYdpi(dpi);
867            s.setPixelDensity(Density.getEnum(mDensity.getText()));
868            s.setSize(ScreenSize.getEnum(mSize.getText()));
869            s.setRatio(ScreenRatio.getEnum(mRatio.getText()));
870            if (mAccelerometer.getSelection()) {
871                mHardware.addSensor(Sensor.ACCELEROMETER);
872            }
873            if (mGyro.getSelection()) {
874                mHardware.addSensor(Sensor.GYROSCOPE);
875            }
876            if (mGps.getSelection()) {
877                mHardware.addSensor(Sensor.GPS);
878            }
879            if (mProximitySensor.getSelection()) {
880                mHardware.addSensor(Sensor.PROXIMITY_SENSOR);
881            }
882            if (mCameraFront.getSelection()) {
883                Camera c = new Camera();
884                c.setAutofocus(true);
885                c.setFlash(true);
886                c.setLocation(CameraLocation.FRONT);
887                mHardware.addCamera(c);
888            }
889            if (mCameraRear.getSelection()) {
890                Camera c = new Camera();
891                c.setAutofocus(true);
892                c.setFlash(true);
893                c.setLocation(CameraLocation.BACK);
894                mHardware.addCamera(c);
895            }
896            if (mKeyboard.getSelection()) {
897                mHardware.setKeyboard(Keyboard.QWERTY);
898            } else {
899                mHardware.setKeyboard(Keyboard.NOKEY);
900            }
901            if (mDpad.getSelection()) {
902                mHardware.setNav(Navigation.DPAD);
903            } else if (mTrackball.getSelection()) {
904                mHardware.setNav(Navigation.TRACKBALL);
905            } else {
906                mHardware.setNav(Navigation.NONAV);
907            }
908            long ram = Long.parseLong(mRam.getText());
909            Storage.Unit unit = Storage.Unit.getEnum(mRamCombo.getText());
910            mHardware.setRam(new Storage(ram, unit));
911            if (mButtons.getText().equals("Hardware")) {
912                mHardware.setButtonType(ButtonType.HARD);
913            } else {
914                mHardware.setButtonType(ButtonType.SOFT);
915            }
916
917            // Set the first enabled state to the default state
918            boolean defaultSelected = false;
919            if (mPortrait.getSelection()) {
920                State state = new State();
921                state.setName("Portrait");
922                state.setDescription("The device in portrait orientation");
923                state.setOrientation(ScreenOrientation.PORTRAIT);
924                if (mHardware.getNav().equals(Navigation.NONAV) || !mPortraitNav.getSelection()) {
925                    state.setNavState(NavigationState.HIDDEN);
926                } else {
927                    state.setNavState(NavigationState.EXPOSED);
928                }
929                if (mHardware.getKeyboard().equals(Keyboard.NOKEY)) {
930                    state.setKeyState(KeyboardState.SOFT);
931                } else {
932                    state.setKeyState(KeyboardState.HIDDEN);
933                }
934                state.setHardware(mHardware);
935                if (!defaultSelected) {
936                    state.setDefaultState(true);
937                    defaultSelected = true;
938                }
939                builder.addState(state);
940            }
941            if (mLandscape.getSelection()) {
942                State state = new State();
943                state.setName("Landscape");
944                state.setDescription("The device in landscape orientation");
945                state.setOrientation(ScreenOrientation.LANDSCAPE);
946                if (mHardware.getNav().equals(Navigation.NONAV) || !mLandscapeNav.getSelection()) {
947                    state.setNavState(NavigationState.HIDDEN);
948                } else {
949                    state.setNavState(NavigationState.EXPOSED);
950                }
951                if (mHardware.getKeyboard().equals(Keyboard.NOKEY)) {
952                    state.setKeyState(KeyboardState.SOFT);
953                } else {
954                    state.setKeyState(KeyboardState.HIDDEN);
955                }
956                state.setHardware(mHardware);
957                if (!defaultSelected) {
958                    state.setDefaultState(true);
959                    defaultSelected = true;
960                }
961                builder.addState(state);
962            }
963            if (mKeyboard.getSelection()) {
964                if (mPortraitKeys.getSelection()) {
965                    State state = new State();
966                    state.setName("Portrait with keyboard");
967                    state.setDescription("The device in portrait orientation with a keyboard open");
968                    state.setOrientation(ScreenOrientation.LANDSCAPE);
969                    if (mHardware.getNav().equals(Navigation.NONAV)
970                            || !mPortraitKeysNav.getSelection()) {
971                        state.setNavState(NavigationState.HIDDEN);
972                    } else {
973                        state.setNavState(NavigationState.EXPOSED);
974                    }
975                    state.setKeyState(KeyboardState.EXPOSED);
976                    state.setHardware(mHardware);
977                    if (!defaultSelected) {
978                        state.setDefaultState(true);
979                        defaultSelected = true;
980                    }
981                    builder.addState(state);
982                }
983                if (mLandscapeKeys.getSelection()) {
984                    State state = new State();
985                    state.setName("Landscape with keyboard");
986                    state.setDescription("The device in landscape orientation with a keyboard open");
987                    state.setOrientation(ScreenOrientation.LANDSCAPE);
988                    if (mHardware.getNav().equals(Navigation.NONAV)
989                            || !mLandscapeKeysNav.getSelection()) {
990                        state.setNavState(NavigationState.HIDDEN);
991                    } else {
992                        state.setNavState(NavigationState.EXPOSED);
993                    }
994                    state.setKeyState(KeyboardState.EXPOSED);
995                    state.setHardware(mHardware);
996                    if (!defaultSelected) {
997                        state.setDefaultState(true);
998                        defaultSelected = true;
999                    }
1000                    builder.addState(state);
1001                }
1002            }
1003            Device d = builder.build();
1004            if (mForceCreation.isEnabled() && mForceCreation.getSelection()) {
1005                mManager.replaceUserDevice(d);
1006            } else {
1007                mManager.addUserDevice(d);
1008            }
1009            mManager.saveUserDevices();
1010            super.okPressed();
1011        }
1012    }
1013
1014}
1015