/* * 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.SystemImage; 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.sdk.wizard.SdkComponentInstallPath; import com.android.tools.idea.wizard.DynamicWizard; import com.android.tools.idea.wizard.ScopedStateStore; import com.android.tools.idea.wizard.SingleStepPath; import com.google.common.base.Predicate; import com.google.common.collect.Maps; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.componentsList.layout.Orientation; 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 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; public AvdEditWizard(@Nullable Project project, @Nullable Module module, @Nullable AvdInfo avdInfo, boolean forceCreate) { super(project, module, "AvdEditWizard"); myAvdInfo = avdInfo; myForceCreate = forceCreate; setTitle("Virtual Device Configuration"); } @Override public void init() { if (myAvdInfo != null) { fillExistingInfo(myAvdInfo); } else { initDefaultInfo(); } addPath(new AvdConfigurationPath(getDisposable())); addPath(new SdkComponentInstallPath(getDisposable())); addPath(new SingleStepPath(new ConfigureAvdOptionsStep(getDisposable()))); super.init(); } /** * 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); } /** * 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.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()); 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); if (sdCardLocation != null) { state.put(USE_EXISTING_SD_CARD, true); } state.put(SCALE_SELECTION_KEY, AvdScaleFactor.findByValue(properties.get(SCALE_SELECTION_KEY.name))); 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)); String skinPath = properties.get(CUSTOM_SKIN_FILE_KEY.name); if (skinPath != null) { File skinFile = new File(skinPath); if (skinFile.isDirectory()) { state.put(CUSTOM_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 = Storage.Unit.B; for (Storage.Unit u : Storage.Unit.values()) { if (u.toString().charAt(0) == unitChar) { selectedUnit = u; break; } } try { long numLong = Long.parseLong(numString); return new Storage(numLong, selectedUnit); } catch (NumberFormatException e) { return null; } } @Override public void performFinishingActions() { ScopedStateStore state = getState(); Device device = state.get(DEVICE_DEFINITION_KEY); assert device != null; // Validation should be done by individual steps SystemImageDescription systemImageDescription = state.get(SYSTEM_IMAGE_KEY); assert systemImageDescription != null; ScreenOrientation orientation = state.get(DEFAULT_ORIENTATION_KEY); assert orientation != null; Map<String, String> hardwareProperties = DeviceManager.getHardwareProperties(device); Map<String, Object> userEditedProperties = state.flatten(); // Remove the SD card setting that we're not using String sdCard = null; Boolean useExistingSdCard = state.get(USE_EXISTING_SD_CARD); if (useExistingSdCard != null && useExistingSdCard) { userEditedProperties.remove(SD_CARD_STORAGE_KEY.name); sdCard = state.get(EXISTING_SD_LOCATION); assert sdCard != null; } else { userEditedProperties.remove(EXISTING_SD_LOCATION.name); Storage storage = state.get(SD_CARD_STORAGE_KEY); if (storage != null) { sdCard = toIniString(storage); } } // 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) { return toIniString((Storage)value); } 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 = state.get(CUSTOM_SKIN_FILE_KEY); // Add any values that we can calculate hardwareProperties.put(AvdManager.AVD_INI_SKIN_DYNAMIC, toIniString(false)); hardwareProperties.put(HardwareProperties.HW_KEYBOARD, toIniString(false)); boolean isCircular = DeviceDefinitionPreview.isCircular(device); String avdName = calculateAvdName(myAvdInfo, device, myForceCreate); // 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().getApiLevel(); int newApiLevel = systemImageDescription.target.getVersion().getApiLevel(); if (oldApiLevel > newApiLevel) { String message = String.format(Locale.getDefault(), "You are about to downgrade %1$s from API level %2$d to API level %3$d. " + "This requires a wipe of the userdata partition of the AVD. Do you wish to " + "continue with the data wipe?", avdName, oldApiLevel, newApiLevel); int result = JOptionPane .showConfirmDialog(null, message, "Confirm Data Wipe", JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { AvdManagerConnection.wipeUserData(myAvdInfo); } else { return; // Cancel the edit operation } } } } AvdManagerConnection.createOrUpdateAvd(myAvdInfo, avdName, device, systemImageDescription, orientation, isCircular, sdCard, skinFile, hardwareProperties, false); } @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 Device device, boolean forceCreate) { if (avdInfo != null && !forceCreate) { return avdInfo.getName(); } String deviceName = device.getDisplayName().replace(' ', '_'); String manufacturer = device.getManufacturer().replace(' ', '_'); String candidateBase = String.format("AVD_for_%1$s_by_%2$s", deviceName, manufacturer); candidateBase = candidateBase.replaceAll("[^0-9a-zA-Z_-]+", " ").trim().replaceAll("[ _]+", "_"); String candidate = candidateBase; int i = 1; while (AvdManagerConnection.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 private static String toIniString(@NotNull Storage storage) { Storage.Unit unit = storage.getAppropriateUnits(); return String.format("%1$d%2$c", storage.getSizeAsUnit(unit), unit.toString().charAt(0)); } /** * 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"; } }