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

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.avdmanager.AvdEditWizard.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.resources.ScreenOrientation;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
import com.android.sdklib.devices.Storage;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.HardwareProperties;
import com.android.tools.idea.ddms.screenshot.DeviceArtDescriptor;
import com.android.tools.idea.wizard.*;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

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

/**
 * Wizard for creating/editing AVDs
 */
public class AvdEditWizard extends DynamicWizard {
    @Nullable
    private final AvdInfo myAvdInfo;
    private final boolean myForceCreate;
    private final JComponent myParent;

    public AvdEditWizard(@NotNull JComponent parent, @Nullable Project project, @Nullable Module module,
            @Nullable AvdInfo avdInfo, boolean forceCreate) {
        super(project, module, "AvdEditWizard",
                new DialogWrapperHost(project, DialogWrapper.IdeModalityType.PROJECT));
        myAvdInfo = avdInfo;
        myForceCreate = forceCreate;
        myParent = parent;
        setTitle("Virtual Device Configuration");
    }

    @Override
    public void init() {
        if (myAvdInfo != null) {
            fillExistingInfo(myAvdInfo);
            if (myForceCreate) {
                String displayName = myAvdInfo.getProperties().get(AvdWizardConstants.DISPLAY_NAME_KEY.name);
                getState().put(DISPLAY_NAME_KEY, String.format("Copy of %1$s", displayName));
            }
        } else {
            initDefaultInfo();
        }
        addPath(new AvdConfigurationPath(getDisposable()));
        DynamicWizardStep configStep = new ConfigureAvdOptionsStep(getDisposable());
        addPath(new SingleStepPath(configStep));
        super.init();
        if (myForceCreate && myAvdInfo != null) {
            getState().put(IS_IN_EDIT_MODE_KEY, false);
        }
    }

    /**
     * Init the wizard with a set of reasonable defaults
     */
    private void initDefaultInfo() {
        ScopedStateStore state = getState();
        state.put(SCALE_SELECTION_KEY, DEFAULT_SCALE);
        state.put(NETWORK_SPEED_KEY, DEFAULT_NETWORK_SPEED);
        state.put(NETWORK_LATENCY_KEY, DEFAULT_NETWORK_LATENCY);
        state.put(FRONT_CAMERA_KEY, DEFAULT_CAMERA);
        state.put(BACK_CAMERA_KEY, DEFAULT_CAMERA);
        state.put(INTERNAL_STORAGE_KEY, DEFAULT_INTERNAL_STORAGE);
        state.put(IS_IN_EDIT_MODE_KEY, false);
        state.put(USE_HOST_GPU_KEY, true);
        state.put(DISPLAY_SD_SIZE_KEY, new Storage(100, Storage.Unit.MiB));
        state.put(DISPLAY_USE_EXTERNAL_SD_KEY, false);
    }

    /**
     * Init the wizard by filling in the information from the given AVD
     */
    private void fillExistingInfo(@NotNull AvdInfo avdInfo) {
        ScopedStateStore state = getState();
        List<Device> devices = DeviceManagerConnection.getDefaultDeviceManagerConnection().getDevices();
        Device selectedDevice = null;
        String manufacturer = avdInfo.getDeviceManufacturer();
        String deviceId = avdInfo.getProperties().get(AvdManager.AVD_INI_DEVICE_NAME);
        for (Device device : devices) {
            if (manufacturer.equals(device.getManufacturer()) && deviceId.equals(device.getId())) {
                selectedDevice = device;
                break;
            }
        }
        state.put(DEVICE_DEFINITION_KEY, selectedDevice);
        IAndroidTarget target = avdInfo.getTarget();
        if (target != null) {
            ISystemImage selectedImage = target.getSystemImage(avdInfo.getTag(), avdInfo.getAbiType());
            if (selectedImage != null) {
                SystemImageDescription systemImageDescription = new SystemImageDescription(target, selectedImage);
                state.put(SYSTEM_IMAGE_KEY, systemImageDescription);
            }
        }

        Map<String, String> properties = avdInfo.getProperties();

        state.put(RAM_STORAGE_KEY, getStorageFromIni(properties.get(RAM_STORAGE_KEY.name)));
        state.put(VM_HEAP_STORAGE_KEY, getStorageFromIni(properties.get(VM_HEAP_STORAGE_KEY.name)));
        state.put(INTERNAL_STORAGE_KEY, getStorageFromIni(properties.get(INTERNAL_STORAGE_KEY.name)));

        String sdCardLocation = null;
        if (properties.get(EXISTING_SD_LOCATION.name) != null) {
            sdCardLocation = properties.get(EXISTING_SD_LOCATION.name);
        } else if (properties.get(SD_CARD_STORAGE_KEY.name) != null) {
            sdCardLocation = FileUtil.join(avdInfo.getDataFolderPath(), "sdcard.img");
        }
        state.put(EXISTING_SD_LOCATION, sdCardLocation);
        String dataFolderPath = avdInfo.getDataFolderPath();
        File sdLocationFile = null;
        if (sdCardLocation != null) {
            sdLocationFile = new File(sdCardLocation);
        }
        if (sdLocationFile != null && Objects.equal(sdLocationFile.getParent(), dataFolderPath)) {
            // the image is in the AVD folder, consider it to be internal
            File sdFile = new File(sdCardLocation);
            Storage sdCardSize = new Storage(sdFile.length());
            myState.put(DISPLAY_USE_EXTERNAL_SD_KEY, false);
            myState.put(SD_CARD_STORAGE_KEY, sdCardSize);
            myState.put(DISPLAY_SD_SIZE_KEY, sdCardSize);
        } else {
            // the image is external
            myState.put(DISPLAY_USE_EXTERNAL_SD_KEY, true);
            myState.put(DISPLAY_SD_LOCATION_KEY, sdCardLocation);
        }

        String scale = properties.get(SCALE_SELECTION_KEY.name);
        if (scale != null) {
            state.put(SCALE_SELECTION_KEY, AvdScaleFactor.findByValue(scale));
        }
        state.put(USE_HOST_GPU_KEY, fromIniString(properties.get(USE_HOST_GPU_KEY.name)));
        state.put(USE_SNAPSHOT_KEY, fromIniString(properties.get(USE_SNAPSHOT_KEY.name)));
        state.put(FRONT_CAMERA_KEY, properties.get(FRONT_CAMERA_KEY.name));
        state.put(BACK_CAMERA_KEY, properties.get(BACK_CAMERA_KEY.name));
        state.put(NETWORK_LATENCY_KEY, properties.get(NETWORK_LATENCY_KEY.name));
        state.put(NETWORK_SPEED_KEY, properties.get(NETWORK_SPEED_KEY.name));
        state.put(HAS_HARDWARE_KEYBOARD_KEY, fromIniString(properties.get(HAS_HARDWARE_KEYBOARD_KEY.name)));
        state.put(DISPLAY_NAME_KEY, AvdManagerConnection.getAvdDisplayName(avdInfo));

        String orientation = properties.get(HardwareProperties.HW_INITIAL_ORIENTATION);
        if (orientation != null) {
            state.put(DEFAULT_ORIENTATION_KEY, ScreenOrientation.getByShortDisplayName(orientation));
        }

        String skinPath = properties.get(CUSTOM_SKIN_FILE_KEY.name);
        if (skinPath != null) {
            File skinFile;
            if (skinPath.equals(NO_SKIN.getPath())) {
                skinFile = NO_SKIN;
            } else {
                skinFile = new File(skinPath);
            }
            if (skinFile.isDirectory()) {
                state.put(CUSTOM_SKIN_FILE_KEY, skinFile);
            }
        }
        String backupSkinPath = properties.get(BACKUP_SKIN_FILE_KEY.name);
        if (backupSkinPath != null) {
            File skinFile = new File(backupSkinPath);
            if (skinFile.isDirectory() || FileUtil.filesEqual(skinFile, NO_SKIN)) {
                state.put(BACKUP_SKIN_FILE_KEY, skinFile);
            }
        }
        state.put(IS_IN_EDIT_MODE_KEY, true);
    }

    /**
     * Decodes the given string from the INI file and returns a {@link Storage} of
     * corresponding size.
     */
    @Nullable
    private static Storage getStorageFromIni(String iniString) {
        if (iniString == null) {
            return null;
        }
        String numString = iniString.substring(0, iniString.length() - 1);
        char unitChar = iniString.charAt(iniString.length() - 1);
        Storage.Unit selectedUnit = null;
        for (Storage.Unit u : Storage.Unit.values()) {
            if (u.toString().charAt(0) == unitChar) {
                selectedUnit = u;
                break;
            }
        }
        if (selectedUnit == null) {
            selectedUnit = Storage.Unit.MiB; // Values expressed without a unit read as MB
            numString = iniString;
        }
        try {
            long numLong = Long.parseLong(numString);
            return new Storage(numLong, selectedUnit);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    @Override
    public void performFinishingActions() {
        Device device = myState.get(DEVICE_DEFINITION_KEY);
        assert device != null; // Validation should be done by individual steps
        SystemImageDescription systemImageDescription = myState.get(SYSTEM_IMAGE_KEY);
        assert systemImageDescription != null;
        ScreenOrientation orientation = myState.get(DEFAULT_ORIENTATION_KEY);
        if (orientation == null) {
            orientation = device.getDefaultState().getOrientation();
        }

        Map<String, String> hardwareProperties = DeviceManager.getHardwareProperties(device);
        Map<String, Object> userEditedProperties = myState.flatten();

        // Remove the SD card setting that we're not using
        String sdCard = null;

        Boolean useExternalSdCard = myState.get(DISPLAY_USE_EXTERNAL_SD_KEY);
        boolean useExisting = useExternalSdCard != null && useExternalSdCard;
        if (!useExisting) {
            if (Objects.equal(myState.get(SD_CARD_STORAGE_KEY), myState.get(DISPLAY_SD_SIZE_KEY))) {
                // unchanged, use existing card
                useExisting = true;
            }
        }
        boolean hasSdCard;
        if (!useExisting) {
            userEditedProperties.remove(EXISTING_SD_LOCATION.name);
            Storage storage = myState.get(DISPLAY_SD_SIZE_KEY);
            myState.put(SD_CARD_STORAGE_KEY, storage);
            if (storage != null) {
                sdCard = toIniString(storage, false);
            }
            hasSdCard = storage != null && storage.getSize() > 0;
        } else {
            sdCard = myState.get(DISPLAY_SD_LOCATION_KEY);
            myState.put(EXISTING_SD_LOCATION, sdCard);
            userEditedProperties.remove(SD_CARD_STORAGE_KEY.name);
            assert sdCard != null;
            hasSdCard = true;
            hardwareProperties.put(HardwareProperties.HW_SDCARD, toIniString(true));
        }
        hardwareProperties.put(HardwareProperties.HW_SDCARD, toIniString(hasSdCard));
        // Remove any internal keys from the map
        userEditedProperties = Maps.filterEntries(userEditedProperties, new Predicate<Map.Entry<String, Object>>() {
            @Override
            public boolean apply(Map.Entry<String, Object> input) {
                return !input.getKey().startsWith(WIZARD_ONLY) && input.getValue() != null;
            }
        });
        // Call toString() on all remaining values
        hardwareProperties.putAll(
                Maps.transformEntries(userEditedProperties, new Maps.EntryTransformer<String, Object, String>() {
                    @Override
                    public String transformEntry(String key, Object value) {
                        if (value instanceof Storage) {
                            if (key.equals(AvdWizardConstants.RAM_STORAGE_KEY.name)
                                    || key.equals(AvdWizardConstants.VM_HEAP_STORAGE_KEY.name)) {
                                return toIniString((Storage) value, true);
                            } else {
                                return toIniString((Storage) value, false);
                            }
                        } else if (value instanceof Boolean) {
                            return toIniString((Boolean) value);
                        } else if (value instanceof AvdScaleFactor) {
                            return toIniString((AvdScaleFactor) value);
                        } else if (value instanceof File) {
                            return toIniString((File) value);
                        } else if (value instanceof Double) {
                            return toIniString((Double) value);
                        } else {
                            return value.toString();
                        }
                    }
                }));

        File skinFile = myState.get(CUSTOM_SKIN_FILE_KEY);
        if (skinFile == null) {
            skinFile = resolveSkinPath(device.getDefaultHardware().getSkinFile(), systemImageDescription);
        }
        File backupSkinFile = myState.get(BACKUP_SKIN_FILE_KEY);
        if (backupSkinFile != null) {
            hardwareProperties.put(AvdManager.AVD_INI_BACKUP_SKIN_PATH, backupSkinFile.getPath());
        }

        // Add defaults if they aren't already set differently
        if (!hardwareProperties.containsKey(AvdManager.AVD_INI_SKIN_DYNAMIC)) {
            hardwareProperties.put(AvdManager.AVD_INI_SKIN_DYNAMIC, toIniString(true));
        }
        if (!hardwareProperties.containsKey(HardwareProperties.HW_KEYBOARD)) {
            hardwareProperties.put(HardwareProperties.HW_KEYBOARD, toIniString(false));
        }

        boolean isCircular = device.isScreenRound();

        String tempAvdName = myState.get(AvdWizardConstants.AVD_ID_KEY);
        if (tempAvdName == null || tempAvdName.isEmpty()) {
            tempAvdName = calculateAvdName(myAvdInfo, hardwareProperties, device, myForceCreate);
        }
        final String avdName = tempAvdName;

        // If we're editing an AVD and we downgrade a system image, wipe the user data with confirmation
        if (myAvdInfo != null && !myForceCreate) {
            IAndroidTarget target = myAvdInfo.getTarget();
            if (target != null) {

                int oldApiLevel = target.getVersion().getFeatureLevel();
                int newApiLevel = systemImageDescription.getVersion().getFeatureLevel();
                final String oldApiName = target.getVersion().getApiString();
                final String newApiName = systemImageDescription.getVersion().getApiString();
                if (oldApiLevel > newApiLevel || (oldApiLevel == newApiLevel && target.getVersion().isPreview()
                        && !systemImageDescription.getVersion().isPreview())) {
                    final AtomicReference<Boolean> shouldContinue = new AtomicReference<Boolean>();
                    ApplicationManager.getApplication().invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            String message = String.format(Locale.getDefault(),
                                    "You are about to downgrade %1$s from API level %2$s to API level %3$s.\n"
                                            + "This requires a wipe of the userdata partition of the AVD.\nDo you wish to "
                                            + "continue with the data wipe?",
                                    avdName, oldApiName, newApiName);
                            int result = Messages.showYesNoDialog((Project) null, message, "Confirm Data Wipe",
                                    AllIcons.General.QuestionDialog);
                            shouldContinue.set(result == Messages.YES);
                        }
                    }, ModalityState.any());
                    if (shouldContinue.get()) {
                        AvdManagerConnection.getDefaultAvdManagerConnection().wipeUserData(myAvdInfo);
                    } else {
                        return;
                    }
                }
            }
        }

        AvdManagerConnection connection = AvdManagerConnection.getDefaultAvdManagerConnection();
        connection.createOrUpdateAvd(myForceCreate ? null : myAvdInfo, avdName, device, systemImageDescription,
                orientation, isCircular, sdCard, skinFile, hardwareProperties, false);
    }

    @NotNull
    @Override
    protected String getProgressTitle() {
        return "Saving AVD...";
    }

    @Nullable
    @Override
    public JComponent getProgressParentComponent() {
        return myParent;
    }

    /**
     * Resolve a possibly relative path into a skin directory. If {@code image} is provided, try to match the given path
     * against a skin path from {@code image.getSkins()}. If no match is found or no image is provided, look in the path given by
     * {@link DeviceArtDescriptor#getBundledDescriptorsFolder()}. If no match is found, return {@code path}.
     *
     * @param path  The path to resolve.
     * @param image A SystemImageDescription to use as an additional source of skin directories.
     * @return The resolved path.
     */
    @Nullable
    public static File resolveSkinPath(@Nullable File path, @Nullable SystemImageDescription image) {
        if (path == null || path.getPath().isEmpty()) {
            return path;
        }
        if (path.equals(NO_SKIN)) {
            return NO_SKIN;
        }
        if (!path.isAbsolute()) {
            if (image != null) {
                File[] skins = image.getSkins();
                for (File skin : skins) {
                    if (skin.getPath().endsWith("/" + path.getPath())) {
                        return skin;
                    }
                }
            }
            File resourceDir = DeviceArtDescriptor.getBundledDescriptorsFolder();
            if (resourceDir != null) {
                File resourcePath = new File(resourceDir, path.getPath());
                if (resourcePath.exists()) {
                    return resourcePath;
                }
            }
        }
        return path;
    }

    @NotNull
    private static String toIniString(@NotNull Double value) {
        return String.format(Locale.US, "%f", value);
    }

    @NotNull
    private static String toIniString(@NotNull File value) {
        return value.getPath();
    }

    /**
     * Encode the given value as a string that can be placed in the AVD's INI file.
     */
    @NotNull
    private static String toIniString(@NotNull AvdScaleFactor value) {
        return value.getValue();
    }

    @NotNull
    private static String calculateAvdName(@Nullable AvdInfo avdInfo,
            @NotNull Map<String, String> hardwareProperties, @NotNull Device device, boolean forceCreate) {
        if (avdInfo != null && !forceCreate) {
            return avdInfo.getName();
        }
        String candidateBase = hardwareProperties.get(AvdManagerConnection.AVD_INI_DISPLAY_NAME);
        if (candidateBase == null || candidateBase.isEmpty()) {
            String deviceName = device.getDisplayName().replace(' ', '_');
            String manufacturer = device.getManufacturer().replace(' ', '_');
            candidateBase = String.format("AVD_for_%1$s_by_%2$s", deviceName, manufacturer);
        }
        return cleanAvdName(AvdManagerConnection.getDefaultAvdManagerConnection(), candidateBase, true);
    }

    /**
     * Get a version of {@code candidateBase} modified such that it is a valid filename. Invalid characters will be
     * removed, and if requested the name will be made unique.
     *
     * @param candidateBase the name on which to base the avd name.
     * @param uniquify      if true, _n will be appended to the name if necessary to make the name unique, where n is the first
     *                      number that makes the filename unique.
     * @return The modified filename.
     */
    public static String cleanAvdName(@NotNull AvdManagerConnection connection, @NotNull String candidateBase,
            boolean uniquify) {
        candidateBase = candidateBase.replaceAll("[^0-9a-zA-Z_-]+", " ").trim().replaceAll("[ _]+", "_");
        if (candidateBase.isEmpty()) {
            candidateBase = "myavd";
        }
        String candidate = candidateBase;
        if (uniquify) {
            int i = 1;
            while (connection.avdExists(candidate)) {
                candidate = String.format("%1$s_%2$d", candidateBase, i++);
            }
        }
        return candidate;
    }

    /**
     * Encode the given value as a string that can be placed in the AVD's INI file.
     * Example: 10M or 1G
     */
    @NotNull
    public static String toIniString(@NotNull Storage storage, boolean convertToMb) {
        Storage.Unit unit = convertToMb ? Storage.Unit.MiB : storage.getAppropriateUnits();
        String unitString = convertToMb ? "" : unit.toString().substring(0, 1);
        return String.format("%1$d%2$s", storage.getSizeAsUnit(unit), unitString);
    }

    /**
     * Encode the given value as a string that can be placed in the AVD's INI file.
     */
    @NotNull
    private static String toIniString(@NotNull Boolean b) {
        return b ? "yes" : "no";
    }

    private static boolean fromIniString(@Nullable String s) {
        return "yes".equals(s);
    }

    @Override
    protected String getWizardActionDescription() {
        return "Create/Edit an Android Virtual Device";
    }
}