com.android.tools.idea.avdmanager.ConfigureDeviceOptionsStep.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.avdmanager.ConfigureDeviceOptionsStep.java

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tools.idea.avdmanager;

import com.android.SdkConstants;
import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.resources.*;
import com.android.sdklib.SystemImage;
import com.android.sdklib.devices.*;
import com.android.sdklib.repository.descriptors.IdDisplay;
import com.android.tools.idea.ddms.screenshot.DeviceArtDescriptor;
import com.android.tools.idea.wizard.DynamicWizardStepWithHeaderAndDescription;
import com.android.tools.idea.wizard.ScopedStateStore;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.text.Document;
import java.awt.event.ItemListener;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.android.tools.idea.avdmanager.AvdWizardConstants.*;

/**
 * UI for configuring a Device Hardware Profile.
 */
public class ConfigureDeviceOptionsStep extends DynamicWizardStepWithHeaderAndDescription {
    private static final String DEFAULT_DEVICE_TYPE_LABEL = "Phone/Tablet";

    @Nullable
    private final Device myTemplateDevice;
    private final boolean myForceCreation;
    private DeviceDefinitionPreview myDeviceDefinitionPreview;
    private JTextField myDiagonalScreenSize;
    private JTextField myScreenResolutionWidth;
    private StorageField myRamField;
    private JCheckBox mySupportsLandscape;
    private JTextField myScreenResolutionHeight;
    private JCheckBox myHasBackFacingCamera;
    private JCheckBox myHasFrontFacingCamera;
    private JCheckBox myHasAccelerometer;
    private JCheckBox myHasGyroscope;
    private JCheckBox myHasGps;
    private JCheckBox myHasProximitySensor;
    private JCheckBox mySupportsPortrait;
    private JComboBox myNavigationControlsCombo;
    private JPanel myRootPanel;
    private JCheckBox myHasHardwareButtons;
    private JCheckBox myHasHardwareKeyboard;
    private JTextField myDeviceName;
    private JBLabel myHelpAndErrorLabel;
    private HyperlinkLabel myHardwareSkinHelpLabel;
    private SkinChooser myCustomSkinPath;
    private ComboBox myDeviceTypeComboBox;

    /**
     * This contains the Software for the device. Since it has no effect on the
     * emulator whatsoever, we just use a single instance with reasonable
     * defaults. */
    private final Software mySoftware;

    private final Camera myFrontCamera = new Camera(CameraLocation.FRONT, true, true);
    private final Camera myBackCamera = new Camera(CameraLocation.BACK, true, true);

    private Device.Builder myBuilder = new Device.Builder();

    public ConfigureDeviceOptionsStep(@Nullable Device templateDevice, boolean forceCreation,
            @Nullable Disposable parentDisposable) {
        super("Configure Hardware Profile", null, parentDisposable);
        myTemplateDevice = templateDevice;
        myForceCreation = forceCreation;
        setBodyComponent(myRootPanel);

        mySoftware = new Software();
        mySoftware.setLiveWallpaperSupport(true);
        mySoftware.setGlVersion("2.0");

        myDeviceTypeComboBox.setModel(new CollectionComboBoxModel(ALL_TAGS));
        myDeviceTypeComboBox.setRenderer(new ListCellRendererWrapper<IdDisplay>() {
            @Override
            public void customize(JList list, IdDisplay value, int index, boolean selected, boolean hasFocus) {
                if (SystemImage.DEFAULT_TAG.equals(value) || value == null) {
                    setText(DEFAULT_DEVICE_TYPE_LABEL);
                } else {
                    setText(value.getDisplay());
                }
            }
        });

    }

    private static String getUniqueId(@Nullable String id) {
        return DeviceManagerConnection.getDefaultDeviceManagerConnection().getUniqueId(id);
    }

    private void createUIComponents() {
        myNavigationControlsCombo = new ComboBox(new EnumComboBoxModel<Navigation>(Navigation.class)) {
            @Override
            public ListCellRenderer getRenderer() {
                return new ColoredListCellRenderer() {
                    @Override
                    protected void customizeCellRenderer(JList list, Object value, int index, boolean selected,
                            boolean hasFocus) {
                        append(((Navigation) value).getShortDisplayValue());
                    }
                };
            }
        };

        myHelpAndErrorLabel = new JBLabel();
        myHelpAndErrorLabel.setBackground(JBColor.background());
        myHelpAndErrorLabel.setForeground(JBColor.foreground());
        myHelpAndErrorLabel.setOpaque(true);
        myHardwareSkinHelpLabel = new HyperlinkLabel("How do I create a custom hardware skin?");
        myHardwareSkinHelpLabel.setHyperlinkTarget(AvdWizardConstants.CREATE_SKIN_HELP_LINK);
        myCustomSkinPath = new SkinChooser(getProject());
    }

    @Override
    public void init() {
        super.init();
        registerComponents();
        String desc;
        if (myTemplateDevice == null) {
            initDefaultParams();
            desc = "Create a new hardware profile by selecting hardware features below.";
        } else {
            initFromDevice();
            desc = myTemplateDevice.getDisplayName() + " (Edited)";
        }
        myState.put(KEY_DESCRIPTION, desc);

        myBuilder.setManufacturer("User");
        myBuilder.addSoftware(mySoftware);

        invokeUpdate(null);
    }

    @Override
    protected JLabel getDescriptionLabel() {
        return myHelpAndErrorLabel;
    }

    /**
     * Initialize our state to match the device we've been given as a starting point
     */
    private void initFromDevice() {
        assert myTemplateDevice != null;
        if (myForceCreation) {
            String name = myTemplateDevice.getDisplayName() + " (Edited)";
            myState.put(DEVICE_NAME_KEY, getUniqueId(name));
        } else {
            myState.put(DEVICE_NAME_KEY, myTemplateDevice.getDisplayName());
        }

        String tagId = myTemplateDevice.getTagId();
        if (tagId != null) {
            for (IdDisplay tag : ALL_TAGS) {
                if (tag.getId().equals(tagId)) {
                    myState.put(TAG_ID_KEY, tag);
                    break;
                }
            }
        }

        for (Map.Entry<String, String> entry : myTemplateDevice.getBootProps().entrySet()) {
            myBuilder.addBootProp(entry.getKey(), entry.getValue());
        }

        Hardware defaultHardware = myTemplateDevice.getDefaultHardware();
        Screen screen = defaultHardware.getScreen();
        myState.put(DIAGONAL_SCREENSIZE_KEY, screen.getDiagonalLength());
        myState.put(RESOLUTION_WIDTH_KEY, screen.getXDimension());
        myState.put(RESOLUTION_HEIGHT_KEY, screen.getYDimension());
        myState.put(RAM_STORAGE_KEY, AvdWizardConstants.getDefaultRam(defaultHardware));

        myState.put(HAS_HARDWARE_BUTTONS_KEY, defaultHardware.getButtonType() == ButtonType.HARD);
        myState.put(HAS_HARDWARE_KEYBOARD_KEY, defaultHardware.getKeyboard() != Keyboard.NOKEY);
        myState.put(NAVIGATION_KEY, defaultHardware.getNav());

        final List<State> states = myTemplateDevice.getAllStates();
        myState.put(SUPPORTS_PORTRAIT_KEY, Iterables.any(states, new Predicate<State>() {
            @Override
            public boolean apply(State input) {
                return input.getOrientation().equals(ScreenOrientation.PORTRAIT);
            }
        }));
        myState.put(SUPPORTS_LANDSCAPE_KEY, Iterables.any(states, new Predicate<State>() {
            @Override
            public boolean apply(State input) {
                return input.getOrientation().equals(ScreenOrientation.LANDSCAPE);
            }
        }));

        myState.put(HAS_FRONT_CAMERA_KEY, defaultHardware.getCamera(CameraLocation.FRONT) != null);
        myState.put(HAS_BACK_CAMERA_KEY, defaultHardware.getCamera(CameraLocation.BACK) != null);

        myState.put(HAS_ACCELEROMETER_KEY, defaultHardware.getSensors().contains(Sensor.ACCELEROMETER));
        myState.put(HAS_GYROSCOPE_KEY, defaultHardware.getSensors().contains(Sensor.GYROSCOPE));
        myState.put(HAS_GPS_KEY, defaultHardware.getSensors().contains(Sensor.GPS));
        myState.put(HAS_PROXIMITY_SENSOR_KEY, defaultHardware.getSensors().contains(Sensor.PROXIMITY_SENSOR));
        File skinFile = AvdEditWizard.resolveSkinPath(defaultHardware.getSkinFile(), null);
        if (skinFile != null) {
            myState.put(CUSTOM_SKIN_FILE_KEY, skinFile);
        } else {
            myState.put(CUSTOM_SKIN_FILE_KEY, NO_SKIN);
        }
    }

    /**
     * Initialize a reasonable set of default values (based on the Nexus 5)
     */
    private void initDefaultParams() {
        myState.put(DEVICE_NAME_KEY, getUniqueId(null));
        myState.put(DIAGONAL_SCREENSIZE_KEY, 5.0);
        myState.put(RESOLUTION_WIDTH_KEY, 1080);
        myState.put(RESOLUTION_HEIGHT_KEY, 1920);
        myState.put(RAM_STORAGE_KEY, new Storage(2, Storage.Unit.GiB));

        myState.put(HAS_HARDWARE_BUTTONS_KEY, false);
        myState.put(HAS_HARDWARE_KEYBOARD_KEY, false);
        myState.put(NAVIGATION_KEY, Navigation.NONAV);

        myState.put(SUPPORTS_PORTRAIT_KEY, true);
        myState.put(SUPPORTS_LANDSCAPE_KEY, true);

        myState.put(HAS_FRONT_CAMERA_KEY, true);
        myState.put(HAS_BACK_CAMERA_KEY, true);

        myState.put(HAS_ACCELEROMETER_KEY, true);
        myState.put(HAS_GYROSCOPE_KEY, true);
        myState.put(HAS_GPS_KEY, true);
        myState.put(HAS_PROXIMITY_SENSOR_KEY, true);
        myState.put(CUSTOM_SKIN_FILE_KEY, NO_SKIN);
    }

    @Override
    public boolean validate() {
        setErrorHtml("");
        if (myState.get(DIAGONAL_SCREENSIZE_KEY) == null) {
            setErrorHtml("Please enter a positive floating point value for the screen size");
            return false;
        }
        if (myState.get(RESOLUTION_WIDTH_KEY) == null || myState.get(RESOLUTION_HEIGHT_KEY) == null) {
            setErrorHtml("Please enter positive integer values for the screen resolution (width x height)");
            return false;
        }
        if (myState.get(RAM_STORAGE_KEY) == null) {
            setErrorHtml("Please specify a non-zero amount of RAM");
            return false;
        }
        Double dpi = myState.get(WIP_SCREEN_DPI_KEY);
        if (dpi == null || dpi <= 0) {
            setErrorHtml("The given resolution and screensize specified have a DPI that is too low.");
            return false;
        }

        Boolean supportsLandscape = myState.get(SUPPORTS_LANDSCAPE_KEY);
        supportsLandscape = supportsLandscape == null ? false : supportsLandscape;
        Boolean supportsPortrait = myState.get(SUPPORTS_PORTRAIT_KEY);
        supportsPortrait = supportsPortrait == null ? false : supportsPortrait;

        if (!(supportsLandscape || supportsPortrait)) {
            setErrorHtml("A device must support at least one orientation (Portrait or Landscape)");
            return false;
        }

        File skinPath = myState.get(CUSTOM_SKIN_FILE_KEY);
        if (skinPath != null && !skinPath.equals(NO_SKIN) && !skinPath.isAbsolute()) {
            skinPath = new File(DeviceArtDescriptor.getBundledDescriptorsFolder(), skinPath.getPath());
        }
        if (skinPath != null && !skinPath.equals(NO_SKIN)) {
            File layoutFile = new File(skinPath, SdkConstants.FN_SKIN_LAYOUT);
            if (!layoutFile.isFile()) {
                setErrorHtml("The skin directory does not point to a valid skin.");
            }
        }
        return myState.get(DEVICE_DEFINITION_KEY) != null;
    }

    @Override
    public void deriveValues(Set<ScopedStateStore.Key> modified) {
        boolean refreshAll = modified == null;
        if (refreshAll) {
            modified = ImmutableSet.of();
        }

        if (refreshAll || modified.contains(DEVICE_NAME_KEY)) {
            String name = myState.get(DEVICE_NAME_KEY);
            myBuilder.setName(name == null ? "" : name);
            myBuilder.setId(getUniqueId(name));
        }

        if (refreshAll || modified.contains(TAG_ID_KEY)) {
            IdDisplay tag = myState.get(TAG_ID_KEY);
            if (SystemImage.DEFAULT_TAG.equals(tag) || tag == null) {
                myBuilder.setTagId(null);
            } else {
                myBuilder.setTagId(tag.getId());
            }
        }

        if (!myState.containsKey(WIP_SCREEN_KEY)) {
            Screen s = createDefaultScreen();
            myState.put(WIP_SCREEN_KEY, s);
        }
        Screen screen = myState.get(WIP_SCREEN_KEY);
        assert screen != null;

        if (refreshAll || !Collections.disjoint(modified,
                ImmutableSet.of(DIAGONAL_SCREENSIZE_KEY, RESOLUTION_WIDTH_KEY, RESOLUTION_HEIGHT_KEY))) {
            Double diagonalLength = myState.get(DIAGONAL_SCREENSIZE_KEY);
            if (diagonalLength != null) {
                screen.setDiagonalLength(diagonalLength);
                screen.setSize(getScreenSize(diagonalLength));
            } else {
                diagonalLength = -1.0;
            }

            Integer resolutionWidth = myState.get(RESOLUTION_WIDTH_KEY);
            if (resolutionWidth != null) {
                screen.setXDimension(resolutionWidth);
            } else {
                resolutionWidth = 0;
            }

            Integer resolutionHeight = myState.get(RESOLUTION_HEIGHT_KEY);
            if (resolutionHeight != null) {
                screen.setYDimension(resolutionHeight);
            } else {
                resolutionHeight = 0;
            }

            // The diagonal DPI will be somewhere in between the X and Y dpi if
            // they differ
            double dpi = Math.sqrt(resolutionWidth * resolutionWidth + resolutionHeight * resolutionHeight)
                    / diagonalLength;
            // The diagonal DPI should keep only two digits precision.
            if (dpi >= 0) {
                dpi = Math.round(dpi * 100) / 100.0;
                myState.put(WIP_SCREEN_DPI_KEY, dpi);
                screen.setYdpi(dpi);
                screen.setXdpi(dpi);

                screen.setRatio(getScreenRatio(resolutionWidth, resolutionHeight));
                if (HardwareConfigHelper.isTv(myTemplateDevice)) {
                    // TVs can have varied densities, including much lower than the normal range.
                    // Set the density explicitly in that case.
                    screen.setPixelDensity(Density.TV);
                } else {
                    screen.setPixelDensity(getDensity(dpi));
                }
            }
        }

        if (!myState.containsKey(WIP_HARDWARE_KEY)) {
            Hardware h = createDefaultHardware();
            myState.put(WIP_HARDWARE_KEY, h);
        }
        Hardware hardware = myState.get(WIP_HARDWARE_KEY);
        assert hardware != null;

        if (refreshAll || modified.contains(HAS_ACCELEROMETER_KEY)) {
            Boolean hasAccelerometer = myState.get(HAS_ACCELEROMETER_KEY);
            if (hasAccelerometer != null && hasAccelerometer) {
                hardware.addSensor(Sensor.ACCELEROMETER);
            } else {
                hardware.getSensors().remove(Sensor.ACCELEROMETER);
            }
        }

        if (refreshAll || modified.contains(HAS_GYROSCOPE_KEY)) {
            Boolean hasGyroscope = myState.get(HAS_GYROSCOPE_KEY);
            if (hasGyroscope != null && hasGyroscope) {
                hardware.addSensor(Sensor.GYROSCOPE);
            } else {
                hardware.getSensors().remove(Sensor.GYROSCOPE);
            }
        }

        if (refreshAll || modified.contains(HAS_GPS_KEY)) {
            Boolean hasGps = myState.get(HAS_GPS_KEY);
            if (hasGps != null && hasGps) {
                hardware.addSensor(Sensor.GPS);
            } else {
                hardware.getSensors().remove(Sensor.GPS);
            }
        }

        if (refreshAll || modified.contains(HAS_PROXIMITY_SENSOR_KEY)) {
            Boolean hasProximitySensor = myState.get(HAS_PROXIMITY_SENSOR_KEY);
            if (hasProximitySensor != null && hasProximitySensor) {
                hardware.addSensor(Sensor.PROXIMITY_SENSOR);
            } else {
                hardware.getSensors().remove(Sensor.PROXIMITY_SENSOR);
            }
        }

        if (refreshAll || modified.contains(HAS_FRONT_CAMERA_KEY)) {
            Boolean hasFrontCamera = myState.get(HAS_FRONT_CAMERA_KEY);
            if (hasFrontCamera != null && hasFrontCamera) {
                hardware.addCamera(myFrontCamera);
            } else {
                hardware.getCameras().remove(myFrontCamera);
            }
        }

        if (refreshAll || modified.contains(HAS_BACK_CAMERA_KEY)) {
            Boolean hasBackCamera = myState.get(HAS_BACK_CAMERA_KEY);
            if (hasBackCamera != null && hasBackCamera) {
                hardware.addCamera(myBackCamera);
            } else {
                hardware.getCameras().remove(myBackCamera);
            }
        }

        Boolean hasHardwareKeyboard = myState.get(HAS_HARDWARE_KEYBOARD_KEY);
        if (refreshAll || modified.contains(HAS_HARDWARE_KEYBOARD_KEY)) {
            if (hasHardwareKeyboard != null && hasHardwareKeyboard) {
                hardware.setKeyboard(Keyboard.QWERTY);
            } else {
                hardware.setKeyboard(Keyboard.NOKEY);
            }
        }
        if (hasHardwareKeyboard == null) {
            hasHardwareKeyboard = false;
        }

        if (refreshAll || modified.contains(HAS_HARDWARE_BUTTONS_KEY)) {
            Boolean hasHardwareButtons = myState.get(HAS_HARDWARE_BUTTONS_KEY);
            if (hasHardwareButtons != null && hasHardwareButtons) {
                hardware.setButtonType(ButtonType.HARD);
            } else {
                hardware.setButtonType(ButtonType.SOFT);
            }
        }

        if (refreshAll || modified.contains(NAVIGATION_KEY)) {
            Navigation nav = myState.get(NAVIGATION_KEY);
            if (nav != null) {
                hardware.setNav(nav);
            }
        }

        if (refreshAll || modified.contains(RAM_STORAGE_KEY)) {
            Storage ram = myState.get(RAM_STORAGE_KEY);
            if (ram != null) {
                hardware.setRam(ram);
            }
        }

        if (refreshAll || modified.contains(CUSTOM_SKIN_FILE_KEY)) {
            File skinFile = myState.get(CUSTOM_SKIN_FILE_KEY);
            if (skinFile != null) {
                hardware.setSkinFile(skinFile);
            }
        }

        // Add the screen to the hardware definition
        hardware.setScreen(screen);

        // Set up our states
        setStates(hardware, hasHardwareKeyboard);

        myState.remove(DEVICE_DEFINITION_KEY);
        try {
            Device d = myBuilder.build();
            myDeviceDefinitionPreview.setDevice(d);
            myState.put(DEVICE_DEFINITION_KEY, d);
        } catch (Exception e) {
            // Pass
        }
    }

    /**
     * Add the proper device states based on keyboard availability and orientation support.
     */
    private void setStates(Hardware hardware, Boolean hasHardwareKeyboard) {
        Boolean supportsLandscape = myState.get(SUPPORTS_LANDSCAPE_KEY);
        supportsLandscape = supportsLandscape == null ? false : supportsLandscape;
        Boolean supportsPortrait = myState.get(SUPPORTS_PORTRAIT_KEY);
        supportsPortrait = supportsPortrait == null ? false : supportsPortrait;

        boolean defaultSelected = false;
        if (supportsPortrait) {
            State state = new State();
            state.setName("Portrait");
            state.setDescription("The device in portrait orientation");
            state.setOrientation(ScreenOrientation.PORTRAIT);
            if (hardware.getNav().equals(Navigation.NONAV)) {
                state.setNavState(NavigationState.HIDDEN);
            } else {
                state.setNavState(NavigationState.EXPOSED);
            }
            if (hardware.getKeyboard().equals(Keyboard.NOKEY)) {
                state.setKeyState(KeyboardState.SOFT);
            } else {
                state.setKeyState(KeyboardState.HIDDEN);
            }
            state.setHardware(hardware);
            state.setDefaultState(true);
            defaultSelected = true;
            myBuilder.removeState(state.getName());
            myBuilder.addState(state);
        }
        if (supportsLandscape) {
            State state = new State();
            state.setName("Landscape");
            state.setDescription("The device in landscape orientation");
            state.setOrientation(ScreenOrientation.LANDSCAPE);
            if (hardware.getNav().equals(Navigation.NONAV)) {
                state.setNavState(NavigationState.HIDDEN);
            } else {
                state.setNavState(NavigationState.EXPOSED);
            }
            if (hardware.getKeyboard().equals(Keyboard.NOKEY)) {
                state.setKeyState(KeyboardState.SOFT);
            } else {
                state.setKeyState(KeyboardState.HIDDEN);
            }
            state.setHardware(hardware);
            if (!defaultSelected) {
                state.setDefaultState(true);
                defaultSelected = true;
            }
            myBuilder.removeState(state.getName());
            myBuilder.addState(state);
        }
        if (hasHardwareKeyboard) {
            if (supportsPortrait) {
                State state = new State();
                state.setName("Portrait with keyboard");
                state.setDescription("The device in portrait orientation with a keyboard open");
                state.setOrientation(ScreenOrientation.LANDSCAPE);
                if (hardware.getNav().equals(Navigation.NONAV)) {
                    state.setNavState(NavigationState.HIDDEN);
                } else {
                    state.setNavState(NavigationState.EXPOSED);
                }
                state.setKeyState(KeyboardState.EXPOSED);
                state.setHardware(hardware);
                if (!defaultSelected) {
                    state.setDefaultState(true);
                    defaultSelected = true;
                }
                myBuilder.removeState(state.getName());
                myBuilder.addState(state);
            }
            if (supportsLandscape) {
                State state = new State();
                state.setName("Landscape with keyboard");
                state.setDescription("The device in landscape orientation with a keyboard open");
                state.setOrientation(ScreenOrientation.LANDSCAPE);
                if (hardware.getNav().equals(Navigation.NONAV)) {
                    state.setNavState(NavigationState.HIDDEN);
                } else {
                    state.setNavState(NavigationState.EXPOSED);
                }
                state.setKeyState(KeyboardState.EXPOSED);
                state.setHardware(hardware);
                if (!defaultSelected) {
                    state.setDefaultState(true);
                }
                myBuilder.removeState(state.getName());
                myBuilder.addState(state);
            }
        }
    }

    /**
     * Create a screen with a reasonable set of defaults.
     */
    private static Screen createDefaultScreen() {
        Screen s = new Screen();
        s.setXdpi(316);
        s.setYdpi(316);
        s.setMultitouch(Multitouch.JAZZ_HANDS);
        s.setMechanism(TouchScreen.FINGER);
        s.setScreenType(ScreenType.CAPACITIVE);
        return s;
    }

    /**
     * Create a hardware profile with a reasonable set of defaults.
     */
    private static Hardware createDefaultHardware() {
        Hardware h = new Hardware();
        h.addNetwork(Network.BLUETOOTH);
        h.addNetwork(Network.WIFI);
        h.addNetwork(Network.NFC);

        h.addSensor(Sensor.BAROMETER);
        h.addSensor(Sensor.COMPASS);
        h.addSensor(Sensor.LIGHT_SENSOR);

        h.setHasMic(true);
        h.addInternalStorage(new Storage(4, Storage.Unit.GiB));
        h.setCpu("Generic CPU");
        h.setGpu("Generic GPU");

        h.addSupportedAbi(Abi.ARMEABI);
        h.addSupportedAbi(Abi.ARMEABI_V7A);
        h.addSupportedAbi(Abi.MIPS);
        h.addSupportedAbi(Abi.X86);

        h.setChargeType(PowerType.BATTERY);
        return h;
    }

    /**
     * Get the resource bucket value that corresponds to the given size in inches.
     */
    @NotNull
    private static ScreenSize getScreenSize(@Nullable Double diagonalSize) {
        if (diagonalSize == null) {
            return ScreenSize.NORMAL;
        }
        double diagonalDp = 160.0 * diagonalSize;

        // Set the Screen Size
        if (diagonalDp >= 1200) {
            return ScreenSize.XLARGE;
        } else if (diagonalDp >= 800) {
            return ScreenSize.LARGE;
        } else if (diagonalDp >= 568) {
            return ScreenSize.NORMAL;
        } else {
            return ScreenSize.SMALL;
        }
    }

    /**
     * Calculate the screen ratio. Beyond a 5:3 ratio is considered "long"
     */
    @NotNull
    private static ScreenRatio getScreenRatio(int width, int height) {
        int longSide = Math.max(width, height);
        int shortSide = Math.min(width, height);

        // Above a 5:3 ratio is "long"
        if (longSide * 1.0 / shortSide >= 5.0 / 3) {
            return ScreenRatio.LONG;
        } else {
            return ScreenRatio.NOTLONG;
        }
    }

    /**
     * Calculate the density resource bucket for the given dots-per-inch
     */
    @NotNull
    private static Density getDensity(double dpi) {
        double difference = Double.MAX_VALUE;
        Density bucket = Density.MEDIUM;
        for (Density d : Density.values()) {
            if (!d.isValidValueForDevice()) {
                continue;
            }
            if (Math.abs(d.getDpiValue() - dpi) < difference) {
                difference = Math.abs(d.getDpiValue() - dpi);
                bucket = d;
            }
        }
        return bucket;
    }

    /**
     * Bind our controls to their state store keys
     */
    private void registerComponents() {
        register(DEVICE_NAME_KEY, myDeviceName);
        setControlDescription(myDeviceName, "Name of the Device Profile");

        register(DIAGONAL_SCREENSIZE_KEY, myDiagonalScreenSize, DOUBLE_BINDING);
        setControlDescription(myDiagonalScreenSize,
                "Actual Android Virtual Device size of the screen, measured as the screen's diagonal");
        register(RESOLUTION_WIDTH_KEY, myScreenResolutionWidth, INT_BINDING);
        setControlDescription(myScreenResolutionWidth, "The total number of physical pixels on a screen. "
                + "When adding support for multiple screens, applications do not work directly "
                + "with resolution; applications should be concerned only with screen size and density,"
                + " as specified by the generalized size and density groups.\n" + "\n" + "Width in pixels\n");
        register(RESOLUTION_HEIGHT_KEY, myScreenResolutionHeight, INT_BINDING);
        setControlDescription(myScreenResolutionHeight, "The total number of physical pixels on a screen. "
                + "When adding support for multiple screens, applications do not work directly "
                + "with resolution; applications should be concerned only with screen size and density,"
                + " as specified by the generalized size and density groups.\n" + "<br>" + "Height in pixels\n");

        register(RAM_STORAGE_KEY, myRamField, myRamField.getBinding());
        setControlDescription(myRamField, "The amount of physical RAM on the device.");

        register(HAS_HARDWARE_BUTTONS_KEY, myHasHardwareButtons);
        setControlDescription(myHasHardwareButtons,
                "Enables hardware navigation button support in Android Virtual Device");
        register(HAS_HARDWARE_KEYBOARD_KEY, myHasHardwareKeyboard);
        setControlDescription(myHasHardwareKeyboard,
                "Enables hardware keyboard  support in Android Virtual Device");
        register(NAVIGATION_KEY, myNavigationControlsCombo, NAVIGATION_BINDING);
        setControlDescription(myNavigationControlsCombo,
                "No Navigation - No navigational controls \n" + "<br>"
                        + "Directional Pad - Enables direction pad support in emulator\n" + "<br>"
                        + "Trackball - Enables trackball support in emulator");

        register(SUPPORTS_LANDSCAPE_KEY, mySupportsLandscape);
        setControlDescription(mySupportsLandscape, "Enables the landscape device screen state in emulator ");
        register(SUPPORTS_PORTRAIT_KEY, mySupportsPortrait);
        setControlDescription(mySupportsPortrait, "Enables the portrait device screen state in emulator ");

        register(HAS_BACK_CAMERA_KEY, myHasBackFacingCamera);
        setControlDescription(myHasBackFacingCamera, "Enables back-facing camera support in emulator");
        register(HAS_FRONT_CAMERA_KEY, myHasFrontFacingCamera);
        setControlDescription(myHasFrontFacingCamera, "Enables front- facing camera support in emulator");

        register(HAS_ACCELEROMETER_KEY, myHasAccelerometer);
        setControlDescription(myHasAccelerometer, "Enables accelerometer support in emulator");
        register(HAS_GYROSCOPE_KEY, myHasGyroscope);
        setControlDescription(myHasGyroscope, "Enables gyroscope support in emulator");
        register(HAS_GPS_KEY, myHasGps);
        setControlDescription(myHasGps, "Enables GPS (global positioning support) support in emulator");
        register(HAS_PROXIMITY_SENSOR_KEY, myHasProximitySensor);
        setControlDescription(myHasProximitySensor, "Enables proximity sensor support in emulator");

        File skinFile = myState.get(CUSTOM_SKIN_FILE_KEY);
        register(CUSTOM_SKIN_FILE_KEY, myCustomSkinPath, myCustomSkinPath.getBinding());
        myState.put(CUSTOM_SKIN_FILE_KEY, skinFile);
        setControlDescription(myCustomSkinPath, "Path to a directory containing a custom skin");

        register(TAG_ID_KEY, myDeviceTypeComboBox);
    }

    private static final ComponentBinding<Double, JTextField> DOUBLE_BINDING = new ComponentBinding<Double, JTextField>() {
        @Override
        public void setValue(@Nullable Double newValue, @NotNull JTextField component) {
            if (newValue != null) {
                component.setText(Double.toString(newValue));
            }
        }

        @Nullable
        @Override
        public Double getValue(@NotNull JTextField component) {
            String text = component.getText();
            if (text != null) {
                try {
                    return Double.parseDouble(text);
                } catch (NumberFormatException e) {
                    // Pass, will return null below
                }
            }
            return null;
        }

        @Nullable
        @Override
        public Document getDocument(@NotNull JTextField component) {
            return component.getDocument();
        }
    };

    private static final ComponentBinding<Integer, JTextField> INT_BINDING = new ComponentBinding<Integer, JTextField>() {
        @Override
        public void setValue(@Nullable Integer newValue, @NotNull JTextField component) {
            if (newValue != null) {
                component.setText(Integer.toString(newValue));
            }
        }

        @Nullable
        @Override
        public Integer getValue(@NotNull JTextField component) {
            String text = component.getText();
            if (text != null) {
                try {
                    return Integer.parseInt(text);
                } catch (NumberFormatException e) {
                    // Pass, will return null below
                }
            }
            return null;
        }

        @Nullable
        @Override
        public Document getDocument(@NotNull JTextField component) {
            return component.getDocument();
        }
    };

    private static final ComponentBinding<Navigation, JComboBox> NAVIGATION_BINDING = new ComponentBinding<Navigation, JComboBox>() {
        @Override
        public void setValue(@Nullable Navigation newValue, @NotNull JComboBox component) {
            component.setSelectedItem(newValue);
        }

        @Nullable
        @Override
        public Navigation getValue(@NotNull JComboBox component) {
            return (Navigation) component.getSelectedItem();
        }

        @Override
        public void addItemListener(@NotNull ItemListener listener, @NotNull JComboBox component) {
            component.addItemListener(listener);
        }
    };

    @NotNull
    @Override
    public String getStepName() {
        return "Configure Device Profile";
    }

    @Override
    public JComponent getPreferredFocusedComponent() {
        return null;
    }

    @NotNull
    @Override
    protected String getStepTitle() {
        return "Configure Hardware Profile";
    }

    @Nullable
    @Override
    protected String getStepDescription() {
        return null;
    }
}