blob: bfaeade3fa7e13f989a23ffe40a8f1e9b3c284e9 [file] [log] [blame]
/*
* 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.resources.Density;
import com.android.resources.Keyboard;
import com.android.resources.ScreenOrientation;
import com.android.resources.ScreenSize;
import com.android.sdklib.devices.CameraLocation;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.Screen;
import com.android.sdklib.devices.Storage;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.tools.idea.ui.ASGallery;
import com.android.tools.idea.wizard.*;
import com.android.tools.idea.wizard.dynamic.*;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.*;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBList;
import com.intellij.util.IconUtil;
import icons.AndroidIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static com.android.sdklib.devices.Storage.Unit;
import static com.android.tools.idea.avdmanager.AvdWizardConstants.*;
import static com.android.tools.idea.wizard.dynamic.ScopedStateStore.Key;
/**
* Options panel for configuring various AVD options. Has an "advanced" mode and a "simple" mode.
* Help and error messaging appears on the right hand side.
*/
public class ConfigureAvdOptionsStep extends DynamicWizardStepWithDescription {
private JBLabel myDeviceName;
private JBLabel myDeviceDetails;
private JButton myChangeDeviceButton;
private JBLabel mySystemImageName;
private JBLabel mySystemImageDetails;
private JButton myChangeSystemImageButton;
private TextFieldWithBrowseButton myExistingSdCard;
private JComboBox myScalingComboBox;
private ASGallery<ScreenOrientation> myOrientationToggle;
private JPanel myRoot;
private JCheckBox myUseHostGPUCheckBox;
private JCheckBox myStoreASnapshotForCheckBox;
private JComboBox myFrontCameraCombo;
private JComboBox myBackCameraCombo;
private JComboBox mySpeedCombo;
private JComboBox myLatencyCombo;
private JButton myShowAdvancedSettingsButton;
private StorageField myRamStorage;
private StorageField myVmHeapStorage;
private StorageField myInternalStorage;
private StorageField myNewSdCardStorage;
private JBLabel myMemoryAndStorageLabel;
private JBLabel myRamLabel;
private JBLabel myVmHeapLabel;
private JBLabel myInternalStorageLabel;
private JBLabel mySdCardLabel;
private HyperlinkLabel myHardwareSkinHelpLabel;
private JTextField myAvdDisplayName;
private JBLabel mySkinDefinitionLabel;
private JBLabel myAvdId;
private JLabel myAvdIdLabel;
private SkinChooser mySkinComboBox;
private JPanel myAvdDisplayNamePanel;
private JBLabel myAvdNameLabel;
private JCheckBox myEnableComputerKeyboard;
private JRadioButton myBuiltInRadioButton;
private JRadioButton myExternalRadioButton;
private JCheckBox myDeviceFrameCheckbox;
private JBLabel myDeviceFrameLabel;
private Iterable<JComponent> myAdvancedOptionsComponents;
private String myOriginalName;
private JSeparator myStorageSeparator;
private JBLabel myCameraLabel;
private JBLabel myFrontCameraLabel;
private JBLabel myBackCameraLabel;
private JSeparator myCameraSeparator;
private JBLabel myNetworkLabel;
private JBLabel mySpeedLabel;
private JBLabel myLatencyLabel;
private JBLabel myKeyboardLabel;
private JSeparator myKeyboardSeparator;
private JSeparator myNetworkSeparator;
private AvdConfigurationOptionHelpPanel myAvdConfigurationOptionHelpPanel;
private PropertyChangeListener myFocusListener;
// Labels used for the advanced settings toggle button
private static final String ADVANCED_SETTINGS = "Advanced Settings";
private static final String SHOW = "Show " + ADVANCED_SETTINGS;
private static final String HIDE = "Hide " + ADVANCED_SETTINGS;
private Set<JComponent> myErrorStateComponents = Sets.newHashSet();
private class MyActionListener<T> implements ActionListener {
DynamicWizardStep myStep;
String myDescription;
Key<T> myResultKey;
public MyActionListener(DynamicWizardStep step, String description, Key<T> resultKey) {
myStep = step;
myDescription = description;
myResultKey = resultKey;
}
@Override
public void actionPerformed(ActionEvent e) {
DynamicWizard wizard = new SingleStepWizard(getProject(), getModule(), myStep, new SingleStepDialogWrapperHost(getProject())) {
@NotNull
@Override
protected String getProgressTitle() {
return "Updating AVD...";
}
@Override
protected String getWizardActionDescription() {
return myDescription;
}
};
ScopedStateStore subState = wizard.getState();
subState.putAllInWizardScope(myState);
subState.put(IS_IN_EDIT_MODE_KEY, false);
wizard.init();
if (wizard.showAndGet()) {
myState.put(myResultKey, subState.get(myResultKey));
}
}
}
public <T> MyActionListener<T> createListener(DynamicWizardStep step, String description, Key<T> resultKey) {
return new MyActionListener<T>(step, description, resultKey);
}
public ConfigureAvdOptionsStep(@Nullable final Disposable parentDisposable) {
super(parentDisposable);
// myAvdConfigurationOptionHelpPanel.setPreferredSize(new Dimension(360, -1));
setBodyComponent(myRoot);
registerAdvancedOptionsVisibility();
toggleAdvancedSettings(false);
myShowAdvancedSettingsButton.setText(SHOW);
ActionListener toggleAdvancedSettingsListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (myShowAdvancedSettingsButton.getText().equals(SHOW)) {
toggleAdvancedSettings(true);
myShowAdvancedSettingsButton.setText(HIDE);
}
else {
toggleAdvancedSettings(false);
myShowAdvancedSettingsButton.setText(SHOW);
}
}
};
myShowAdvancedSettingsButton.addActionListener(toggleAdvancedSettingsListener);
myChangeDeviceButton.addActionListener(
createListener(new ChooseDeviceDefinitionStep(parentDisposable), "Select a device", DEVICE_DEFINITION_KEY));
myChangeSystemImageButton.addActionListener(
createListener(new ChooseSystemImageStep(getProject(), parentDisposable), "Select a system image", SYSTEM_IMAGE_KEY));
ActionListener sdActionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateSdCardSettings();
}
};
myExternalRadioButton.addActionListener(sdActionListener);
myBuiltInRadioButton.addActionListener(sdActionListener);
myOrientationToggle.setOpaque(false);
myFocusListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
Object value = evt.getNewValue();
if (evt.getNewValue() instanceof JComponent) {
JComponent component = (JComponent)value;
Component parent = component.getParent();
if (parent instanceof JComponent) {
((JComponent)parent).scrollRectToVisible(component.getBounds());
}
}
}
};
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", myFocusListener);
}
@Override
public void dispose() {
if (myFocusListener != null) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener("focusOwner", myFocusListener);
}
}
/**
* Toggle the SD card between using an existing file and creating a new file.
*/
private void updateSdCardSettings() {
boolean useExisting = myState.get(DISPLAY_USE_EXTERNAL_SD_KEY);
if (useExisting) {
myExistingSdCard.setEnabled(true);
myNewSdCardStorage.setEnabled(false);
}
else {
myExistingSdCard.setEnabled(false);
myNewSdCardStorage.setEnabled(true);
}
}
@Override
public void init() {
super.init();
registerComponents();
deregister(getDescriptionLabel());
getDescriptionLabel().setVisible(false);
Device device = myState.get(DEVICE_DEFINITION_KEY);
SystemImageDescription systemImage = myState.get(SYSTEM_IMAGE_KEY);
if (myState.get(DISPLAY_NAME_KEY) == null || myState.get(DISPLAY_NAME_KEY).isEmpty()) {
assert device != null && systemImage != null;
String avdName = String.format(Locale.getDefault(), "%1$s API %2$d", device.getDisplayName(), systemImage.getVersion().getApiLevel());
avdName = AvdManagerConnection.getDefaultAvdManagerConnection().uniquifyDisplayName(avdName);
myState.put(DISPLAY_NAME_KEY, avdName);
}
myAvdConfigurationOptionHelpPanel.setSystemImageDescription(systemImage);
Boolean editMode = myState.get(AvdWizardConstants.IS_IN_EDIT_MODE_KEY);
editMode = editMode == null ? Boolean.FALSE : editMode;
myOriginalName = editMode ? myState.get(AvdWizardConstants.DISPLAY_NAME_KEY) : "";
if (device.getDefaultHardware().getKeyboard().equals(Keyboard.QWERTY)) {
myEnableComputerKeyboard.setEnabled(false);
}
if (myState.get(DEFAULT_ORIENTATION_KEY) == null) {
myState.put(DEFAULT_ORIENTATION_KEY, device.getDefaultState().getOrientation());
}
myAvdId.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
myAvdId.requestFocusInWindow();
}
});
File customSkin = myState.get(CUSTOM_SKIN_FILE_KEY);
File backupSkin = myState.get(BACKUP_SKIN_FILE_KEY);
// If there is a backup skin but no normal skin, the "use device frame" checkbox should be unchecked.
myState.put(DEVICE_FRAME_KEY, backupSkin == null || customSkin != null);
myState.put(DISPLAY_SKIN_FILE_KEY, AvdEditWizard.resolveSkinPath(myState.get(DEVICE_DEFINITION_KEY).getDefaultHardware().getSkinFile(),
myState.get(SYSTEM_IMAGE_KEY)));
// If customSkin is null but backupSkin is defined, we want to show it (with the checkbox unchecked).
if (customSkin == null) {
customSkin = backupSkin;
}
File hardwareSkin = AvdEditWizard.resolveSkinPath(device.getDefaultHardware().getSkinFile(), systemImage);
// If the skin is set and different from what would be provided by the hardware, set the value of the
// control directly, so it is marked as user edited and not changed when the device is changed.
if (customSkin != null && !FileUtil.filesEqual(customSkin, hardwareSkin)) {
mySkinComboBox.getComboBox().setSelectedItem(customSkin);
}
}
@Override
public boolean commitStep() {
if (!myState.containsKey(DISPLAY_USE_EXTERNAL_SD_KEY) || !myState.get(DISPLAY_USE_EXTERNAL_SD_KEY)) {
Storage orig = myState.get(SD_CARD_STORAGE_KEY);
Storage current = myState.get(DISPLAY_SD_SIZE_KEY);
if (orig != null && !orig.equals(current)) {
int result = Messages.showYesNoDialog((Project)null, "Changing the size of the built-in SD card will erase " +
"the current contents of the card. Continue?",
"Confirm Data Wipe", AllIcons.General.QuestionDialog);
if (result != Messages.YES) {
return false;
}
}
}
File displayFile = myState.get(DISPLAY_SKIN_FILE_KEY);
boolean hasFrame = myState.getNotNull(DEVICE_FRAME_KEY, false);
myState.put(CUSTOM_SKIN_FILE_KEY, hasFrame ? displayFile : NO_SKIN);
myState.put(BACKUP_SKIN_FILE_KEY, hasFrame ? null : displayFile);
return super.commitStep();
}
@Override
public void onEnterStep() {
super.onEnterStep();
Device device = myState.get(DEVICE_DEFINITION_KEY);
if (device != null) {
toggleOptionals(device);
}
updateSdCardSettings();
// Disable GPU acceleration for images with API <= 15
if (myState.get(SYSTEM_IMAGE_KEY).getVersion().getApiLevel() <= 15) {
myUseHostGPUCheckBox.setEnabled(false);
myUseHostGPUCheckBox.setText("Use Host GPU (Requires API > 15)");
myUseHostGPUCheckBox.setSelected(false);
} else {
myUseHostGPUCheckBox.setEnabled(true);
myUseHostGPUCheckBox.setText("Use Host GPU");
}
}
@Override
public boolean validate() {
clearErrorState();
boolean valid = true;
// Check Ram
Storage ram = myState.get(RAM_STORAGE_KEY);
if (ram == null || ram.getSizeAsUnit(Unit.MiB) < 128) {
setErrorState("RAM must be a numeric (integer) value of at least 128MB. Recommendation is 1GB.", myMemoryAndStorageLabel, myRamLabel,
myRamStorage);
valid = false;
}
// Check VM Heap
Storage vmHeap = myState.get(VM_HEAP_STORAGE_KEY);
if (vmHeap == null || vmHeap.getSizeAsUnit(Unit.MiB) < 16) {
setErrorState("VM Heap must be a numeric (integer) value of at least 16MB.", myMemoryAndStorageLabel, myVmHeapLabel, myVmHeapStorage);
valid = false;
}
// Check Internal Storage
Storage internal = myState.get(INTERNAL_STORAGE_KEY);
if (internal == null || internal.getSizeAsUnit(Unit.MiB) < 200) {
setErrorState("Internal storage must be a numeric (integer) value of at least 200MB.", myMemoryAndStorageLabel,
myInternalStorageLabel, myInternalStorage);
valid = false;
}
// If we're using an existing SD card, make sure it exists
Boolean useExistingSd = myState.get(DISPLAY_USE_EXTERNAL_SD_KEY);
if (useExistingSd != null && useExistingSd) {
String path = myState.get(DISPLAY_SD_LOCATION_KEY);
if (path == null || !new File(path).isFile()) {
setErrorState("The specified SD image file must be a valid image file", myMemoryAndStorageLabel, mySdCardLabel, myExistingSdCard);
valid = false;
}
}
else {
Storage sdCard = myState.get(DISPLAY_SD_SIZE_KEY);
if (sdCard != null && (sdCard.getSizeAsUnit(Unit.MiB) < 10)) {
setErrorState("The SD card must be larger than 10MB", myMemoryAndStorageLabel, mySdCardLabel, myNewSdCardStorage);
valid = false;
}
}
File skinFile = myState.get(CUSTOM_SKIN_FILE_KEY);
if (skinFile != null && !skinFile.equals(NO_SKIN)) {
File layoutFile = new File(skinFile, SdkConstants.FN_SKIN_LAYOUT);
if (!layoutFile.isFile()) {
setErrorState("The skin directory does not point to a valid skin.", myDeviceFrameLabel, mySkinComboBox);
valid = false;
}
}
Boolean gpu = myState.get(USE_HOST_GPU_KEY);
Boolean snapshot = myState.get(USE_SNAPSHOT_KEY);
if (gpu != null && snapshot != null && gpu && snapshot) {
setErrorState("GPU Emulation and Snapshot cannot by used simultaneously.", myUseHostGPUCheckBox, myStoreASnapshotForCheckBox);
valid = false;
}
String displayName = myState.get(DISPLAY_NAME_KEY);
if (displayName != null) {
displayName = displayName.trim();
if (!displayName.equals(myOriginalName) && AvdManagerConnection.getDefaultAvdManagerConnection().findAvdWithName(displayName)) {
setErrorState(String.format("An AVD with the name \"%1$s\" already exists.", displayName), myAvdDisplayNamePanel, myAvdNameLabel);
valid = false;
}
if (!displayName.matches("^[0-9a-zA-Z-_. ()]+$")) {
setErrorState("The AVD name can only contain the characters a-z A-Z 0-9 . _ - ( )", myAvdDisplayNamePanel, myAvdNameLabel);
valid = false;
}
}
Device device = myState.get(DEVICE_DEFINITION_KEY);
SystemImageDescription systemImage = myState.get(SYSTEM_IMAGE_KEY);
if (!ChooseSystemImageStep.systemImageMatchesDevice(systemImage, device)) {
setErrorState("The selected system image is incompatible with the selected device.", mySystemImageDetails);
valid = false;
}
return valid;
}
/**
* Clear the error highlighting around any components that had previously been marked as errors
*/
private void clearErrorState() {
for (JComponent c : myErrorStateComponents) {
if (c instanceof JLabel) {
c.setForeground(JBColor.foreground());
((JLabel)c).setIcon(null);
}
else if (c instanceof StorageField) {
((StorageField)c).setError(false);
}
else if (c instanceof JCheckBox) {
c.setForeground(JBColor.foreground());
}
else {
c.setBorder(null);
}
}
myAvdConfigurationOptionHelpPanel.setErrorMessage("");
setErrorHtml(null);
}
/**
* Set an error message and mark the given components as being in error state
*/
private void setErrorState(String message, JComponent... errorComponents) {
boolean isVisible = false;
for (JComponent c : errorComponents) {
if (c.isShowing()) {
isVisible = true;
break;
}
}
if (!isVisible) {
setErrorHtml(message);
}
else {
myAvdConfigurationOptionHelpPanel.setErrorMessage(message);
for (JComponent c : errorComponents) {
if (c instanceof JLabel) {
c.setForeground(JBColor.RED);
((JLabel)c).setIcon(AllIcons.General.BalloonError);
}
else if (c instanceof StorageField) {
((StorageField)c).setError(true);
}
else if (c instanceof JCheckBox) {
c.setForeground(JBColor.RED);
}
else {
c.setBorder(new LineBorder(JBColor.RED));
}
myErrorStateComponents.add(c);
}
}
}
/**
* Bind components to their specified keys and help messaging.
*/
private void registerComponents() {
register(DISPLAY_NAME_KEY, myAvdDisplayName);
register(AVD_ID_KEY, myAvdId);
final AvdManagerConnection connection = AvdManagerConnection.getDefaultAvdManagerConnection();
registerValueDeriver(AVD_ID_KEY, new ValueDeriver<String>() {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DISPLAY_NAME_KEY);
}
@Nullable
@Override
public String deriveValue(@NotNull ScopedStateStore state, @Nullable Key changedKey, @Nullable String currentValue) {
String displayName = state.get(DISPLAY_NAME_KEY);
if (displayName != null) {
return AvdEditWizard
.cleanAvdName(connection, displayName, !displayName.equals(myOriginalName));
}
return "";
}
});
setControlDescription(myAvdDisplayName, myAvdConfigurationOptionHelpPanel.getDescription(DISPLAY_NAME_KEY));
setControlDescription(myAvdId, myAvdConfigurationOptionHelpPanel.getDescription(AVD_ID_KEY));
register(DEVICE_DEFINITION_KEY, myDeviceName, DEVICE_NAME_BINDING);
register(DEVICE_DEFINITION_KEY, myDeviceDetails, DEVICE_DETAILS_BINDING);
register(SYSTEM_IMAGE_KEY, mySystemImageName, SYSTEM_IMAGE_NAME_BINDING);
register(SYSTEM_IMAGE_KEY, mySystemImageDetails, SYSTEM_IMAGE_DESCRIPTION_BINDING);
register(RAM_STORAGE_KEY, myRamStorage, myRamStorage.getBinding());
setControlDescription(myRamStorage, myAvdConfigurationOptionHelpPanel.getDescription(RAM_STORAGE_KEY));
register(VM_HEAP_STORAGE_KEY, myVmHeapStorage, myVmHeapStorage.getBinding());
setControlDescription(myVmHeapStorage, myAvdConfigurationOptionHelpPanel.getDescription(VM_HEAP_STORAGE_KEY));
register(INTERNAL_STORAGE_KEY, myInternalStorage, myInternalStorage.getBinding());
setControlDescription(myInternalStorage, myAvdConfigurationOptionHelpPanel.getDescription(INTERNAL_STORAGE_KEY));
register(DISPLAY_SD_SIZE_KEY, myNewSdCardStorage, myNewSdCardStorage.getBinding());
setControlDescription(myNewSdCardStorage, myAvdConfigurationOptionHelpPanel.getDescription(SD_CARD_STORAGE_KEY));
register(USE_SNAPSHOT_KEY, myStoreASnapshotForCheckBox);
setControlDescription(myStoreASnapshotForCheckBox, myAvdConfigurationOptionHelpPanel.getDescription(USE_SNAPSHOT_KEY));
register(USE_HOST_GPU_KEY, myUseHostGPUCheckBox);
setControlDescription(myUseHostGPUCheckBox, myAvdConfigurationOptionHelpPanel.getDescription(USE_HOST_GPU_KEY));
if (Boolean.FALSE.equals(myState.get(IS_IN_EDIT_MODE_KEY))) {
registerValueDeriver(RAM_STORAGE_KEY, new MemoryValueDeriver() {
@Nullable
@Override
protected Storage getStorage(@NotNull Device device) {
return AvdWizardConstants.getDefaultRam(device.getDefaultHardware());
}
});
registerValueDeriver(VM_HEAP_STORAGE_KEY, new MemoryValueDeriver() {
@Nullable
@Override
protected Storage getStorage(@NotNull Device device) {
return calculateVmHeap(device);
}
});
registerValueDeriver(DISPLAY_NAME_KEY, new ValueDeriver<String>() {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DEVICE_DEFINITION_KEY, SYSTEM_IMAGE_KEY);
}
@Nullable
@Override
public String deriveValue(@NotNull ScopedStateStore state, @Nullable Key changedKey, @Nullable String currentValue) {
Device device = state.get(DEVICE_DEFINITION_KEY);
SystemImageDescription systemImage = state.get(SYSTEM_IMAGE_KEY);
if (device != null && systemImage != null) { // Should always be the case
return connection.uniquifyDisplayName(
String.format(Locale.getDefault(), "%1$s API %2$d", device.getDisplayName(), systemImage.getVersion().getApiLevel()));
}
return null; // Should never occur
}
});
}
registerValueDeriver(DISPLAY_SKIN_FILE_KEY, new ValueDeriver<File>() {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DEVICE_DEFINITION_KEY, SYSTEM_IMAGE_KEY);
}
@Nullable
@Override
public File deriveValue(@NotNull ScopedStateStore state, @Nullable Key changedKey, @Nullable File currentValue) {
// If there was a skin specified coming in, this field will be marked as user-edited, and so that needn't be
// taken into account here. The only case we care about is if the device is changed.
return AvdEditWizard.resolveSkinPath(myState.get(DEVICE_DEFINITION_KEY).getDefaultHardware().getSkinFile(),
myState.get(SYSTEM_IMAGE_KEY));
}
});
registerValueDeriver(USE_HOST_GPU_KEY, new ValueDeriver<Boolean>() {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DEVICE_DEFINITION_KEY);
}
@Nullable
@Override
public Boolean deriveValue(@NotNull ScopedStateStore state, @Nullable Key changedKey, @Nullable Boolean currentValue) {
Device device = myState.get(DEVICE_DEFINITION_KEY);
if (device != null && device.isScreenRound()) {
// 79243: Emulator skin doesn't work for round device with Host GPU enabled.
// Therefore, turn it off by default; if a developer is deliberately trying
// to emulate a round device, that's probably more important than acceleration.
// (And with the small screen resolution of the wear devices, lack of Host GPU
// is less severe than say for a tablet AVD.)
return false;
}
return myState.get(USE_HOST_GPU_KEY);
}
});
registerValueDeriver(DEVICE_FRAME_KEY, new ValueDeriver<Boolean>() {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DISPLAY_SKIN_FILE_KEY);
}
@Override
public boolean respectUserEdits() {
// if "No skin" is selected, we always want to uncheck and disable the checkbox,
// regardless of whether it was modified by the user.
return false;
}
@Nullable
@Override
public Boolean deriveValue(@NotNull ScopedStateStore state, @Nullable Key changedKey, @Nullable Boolean currentValue) {
File displaySkinPath = myState.get(DISPLAY_SKIN_FILE_KEY);
boolean hasSkin = displaySkinPath != null && !FileUtil.filesEqual(NO_SKIN, displaySkinPath);
myDeviceFrameCheckbox.setEnabled(hasSkin);
return hasSkin && myState.getNotNull(DEVICE_FRAME_KEY, false);
}
});
register(DEFAULT_ORIENTATION_KEY, myOrientationToggle, ORIENTATION_BINDING);
setControlDescription(myOrientationToggle, myAvdConfigurationOptionHelpPanel.getDescription(DEFAULT_ORIENTATION_KEY));
myOrientationToggle.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
saveState(myOrientationToggle);
}
});
FileChooserDescriptor fileChooserDescriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
@Override
public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
return super.isFileVisible(file, true);
}
};
fileChooserDescriptor.setHideIgnored(false);
myExistingSdCard.addBrowseFolderListener("Select SD Card", "Select an existing SD card image", getProject(), fileChooserDescriptor);
register(DISPLAY_SD_LOCATION_KEY, myExistingSdCard);
setControlDescription(myExistingSdCard, myAvdConfigurationOptionHelpPanel.getDescription(EXISTING_SD_LOCATION));
register(FRONT_CAMERA_KEY, myFrontCameraCombo, STRING_COMBO_BINDING);
setControlDescription(myFrontCameraCombo, myAvdConfigurationOptionHelpPanel.getDescription(FRONT_CAMERA_KEY));
register(BACK_CAMERA_KEY, myBackCameraCombo, STRING_COMBO_BINDING);
setControlDescription(myBackCameraCombo, myAvdConfigurationOptionHelpPanel.getDescription(BACK_CAMERA_KEY));
register(SCALE_SELECTION_KEY, myScalingComboBox, new ComponentBinding<AvdScaleFactor, JComboBox>() {
@Override
public void addActionListener(@NotNull ActionListener listener, @NotNull JComboBox component) {
component.addActionListener(listener);
}
@Nullable
@Override
public AvdScaleFactor getValue(@NotNull JComboBox component) {
return ((AvdScaleFactor)component.getSelectedItem());
}
@Override
public void setValue(@Nullable AvdScaleFactor newValue, @NotNull JComboBox component) {
if (newValue != null) {
component.setSelectedItem(newValue);
}
}
});
setControlDescription(myScalingComboBox, myAvdConfigurationOptionHelpPanel.getDescription(SCALE_SELECTION_KEY));
register(NETWORK_LATENCY_KEY, myLatencyCombo, STRING_COMBO_BINDING);
setControlDescription(myLatencyCombo, myAvdConfigurationOptionHelpPanel.getDescription(NETWORK_LATENCY_KEY));
register(NETWORK_SPEED_KEY, mySpeedCombo, STRING_COMBO_BINDING);
setControlDescription(mySpeedCombo, myAvdConfigurationOptionHelpPanel.getDescription(NETWORK_SPEED_KEY));
register(KEY_DESCRIPTION, myAvdConfigurationOptionHelpPanel, new ComponentBinding<String, AvdConfigurationOptionHelpPanel>() {
@Override
public void setValue(@Nullable String newValue, @NotNull AvdConfigurationOptionHelpPanel component) {
component.setDescriptionText(newValue);
}
});
register(DISPLAY_SKIN_FILE_KEY, mySkinComboBox, mySkinComboBox.getBinding());
setControlDescription(mySkinComboBox, myAvdConfigurationOptionHelpPanel.getDescription(CUSTOM_SKIN_FILE_KEY));
register(DEVICE_FRAME_KEY, myDeviceFrameCheckbox);
setControlDescription(myDeviceFrameCheckbox, myAvdConfigurationOptionHelpPanel.getDescription(DEVICE_FRAME_KEY));
if (!myState.containsKey(HAS_HARDWARE_KEYBOARD_KEY)) {
myState.put(HAS_HARDWARE_KEYBOARD_KEY, true);
}
register(HAS_HARDWARE_KEYBOARD_KEY, myEnableComputerKeyboard);
setControlDescription(myEnableComputerKeyboard, myAvdConfigurationOptionHelpPanel.getDescription(HAS_HARDWARE_KEYBOARD_KEY));
RadioButtonGroupBinding<Boolean> sdCardBinding = new RadioButtonGroupBinding<Boolean>(ImmutableMap.of(
myExternalRadioButton, true,
myBuiltInRadioButton, false));
register(DISPLAY_USE_EXTERNAL_SD_KEY, myExternalRadioButton, sdCardBinding);
setControlDescription(myExternalRadioButton, myAvdConfigurationOptionHelpPanel.getDescription(EXISTING_SD_LOCATION));
register(DISPLAY_USE_EXTERNAL_SD_KEY, myBuiltInRadioButton, sdCardBinding);
setControlDescription(myBuiltInRadioButton, myAvdConfigurationOptionHelpPanel.getDescription(SD_CARD_STORAGE_KEY));
invokeUpdate(null);
}
private void createUIComponents() {
myOrientationToggle =
new ASGallery<ScreenOrientation>(JBList.createDefaultListModel(ScreenOrientation.PORTRAIT, ScreenOrientation.LANDSCAPE),
new Function<ScreenOrientation, Image>() {
@Override
public Image apply(ScreenOrientation input) {
return IconUtil.toImage(ORIENTATIONS.get(input).myIcon);
}
}, new Function<ScreenOrientation, String>() {
@Override
public String apply(ScreenOrientation input) {
return ORIENTATIONS.get(input).myName;
}
}, new Dimension(50, 50));
myOrientationToggle.setCellMargin(new Insets(3, 5, 3, 5));
myOrientationToggle.setBackground(JBColor.background());
myOrientationToggle.setForeground(JBColor.foreground());
myScalingComboBox = new ComboBox(new EnumComboBoxModel<AvdScaleFactor>(AvdScaleFactor.class));
myHardwareSkinHelpLabel = new HyperlinkLabel("How do I create a custom hardware skin?");
myHardwareSkinHelpLabel.setHyperlinkTarget(AvdWizardConstants.CREATE_SKIN_HELP_LINK);
mySkinComboBox = new SkinChooser(getProject());
}
@NotNull
@Override
public String getStepName() {
return "Configure AVD Options";
}
@NotNull
@Override
protected String getStepTitle() {
return "Android Virtual Device (AVD)";
}
@Nullable
@Override
protected String getStepDescription() {
return "Verify Configuration";
}
@Override
public JComponent getPreferredFocusedComponent() {
return myAvdDisplayName;
}
private static final class NamedIcon {
@NotNull private final String myName;
@NotNull private final Icon myIcon;
public NamedIcon(@NotNull String name, @NotNull Icon icon) {
myName = name;
myIcon = icon;
}
}
private static final Map<ScreenOrientation, NamedIcon> ORIENTATIONS = ImmutableMap
.of(ScreenOrientation.PORTRAIT, new NamedIcon("Portrait", AndroidIcons.Portrait), ScreenOrientation.LANDSCAPE,
new NamedIcon("Landscape", AndroidIcons.Landscape));
private static final ComponentBinding<Device, JBLabel> DEVICE_NAME_BINDING = new ComponentBinding<Device, JBLabel>() {
@Override
public void setValue(@Nullable Device newValue, @NotNull JBLabel component) {
if (newValue != null) {
component.setText(newValue.getDisplayName());
Icon icon = DeviceDefinitionPreview.getIcon(newValue);
component.setIcon(icon);
}
}
};
private static final ComponentBinding<Device, JBLabel> DEVICE_DETAILS_BINDING = new ComponentBinding<Device, JBLabel>() {
@Override
public void setValue(@Nullable Device newValue, @NotNull JBLabel component) {
if (newValue != null) {
String description = Joiner.on(' ')
.join(DeviceDefinitionList.getDiagonalSize(newValue), DeviceDefinitionList.getDimensionString(newValue),
DeviceDefinitionList.getDensityString(newValue));
component.setText(description);
}
}
};
private static final ComponentBinding<SystemImageDescription, JBLabel> SYSTEM_IMAGE_NAME_BINDING =
new ComponentBinding<SystemImageDescription, JBLabel>() {
@Override
public void setValue(@Nullable SystemImageDescription newValue, @NotNull JBLabel component) {
if (newValue != null) {
String codeName = SystemImagePreview.getCodeName(newValue);
component.setText(codeName);
try {
Icon icon = IconLoader.findIcon(String.format("/icons/versions/%s_32.png", codeName), AndroidIcons.class);
component.setIcon(icon);
}
catch (RuntimeException e) {
// Pass
}
}
}
};
private final ComponentBinding<SystemImageDescription, JBLabel> SYSTEM_IMAGE_DESCRIPTION_BINDING =
new ComponentBinding<SystemImageDescription, JBLabel>() {
@Override
public void setValue(@Nullable SystemImageDescription newValue, @NotNull JBLabel component) {
if (newValue != null) {
component.setText(newValue.getName() + " " + newValue.getAbiType());
myAvdConfigurationOptionHelpPanel.setSystemImageDescription(newValue);
}
}
};
public static final ComponentBinding<ScreenOrientation, ASGallery<ScreenOrientation>> ORIENTATION_BINDING =
new ComponentBinding<ScreenOrientation, ASGallery<ScreenOrientation>>() {
@Nullable
@Override
public ScreenOrientation getValue(@NotNull ASGallery<ScreenOrientation> component) {
return component.getSelectedElement();
}
@Override
public void setValue(@Nullable ScreenOrientation newValue, @NotNull ASGallery<ScreenOrientation> component) {
component.setSelectedElement(newValue);
}
};
private static final ComponentBinding<String, JComboBox> STRING_COMBO_BINDING = new ComponentBinding<String, JComboBox>() {
@Override
public void setValue(@Nullable String newValue, @NotNull JComboBox component) {
if (newValue == null) {
return;
}
for (int i = 0; i < component.getItemCount(); i++) {
if (newValue.equalsIgnoreCase((String)component.getItemAt(i))) {
component.setSelectedIndex(i);
}
}
}
@Nullable
@Override
public String getValue(@NotNull JComboBox component) {
return component.getSelectedItem().toString().toLowerCase();
}
@Override
public void addItemListener(@NotNull ItemListener listener, @NotNull JComboBox component) {
component.addItemListener(listener);
}
};
private void registerAdvancedOptionsVisibility() {
// Unfortunately the use of subpanels here seems to cause layout issues, so each component must be listed separately.
myAdvancedOptionsComponents = Iterables.concat(
getMemoryAndStorageComponents(),
getCameraComponents(),
getNetworkComponents(),
getSkinComponents(),
getKeyboardComponents(),
getAvdNameComponents()
);
}
private List<JComponent> getAvdNameComponents() {
return ImmutableList.<JComponent>of( myAvdIdLabel, myAvdId);
}
private List<JComponent> getKeyboardComponents() {
return ImmutableList.<JComponent>of(myEnableComputerKeyboard, myKeyboardSeparator, myKeyboardLabel);
}
private List<JComponent> getSkinComponents() {
return ImmutableList.of(mySkinComboBox, mySkinDefinitionLabel, myHardwareSkinHelpLabel);
}
private List<JComponent> getMemoryAndStorageComponents() {
return ImmutableList.<JComponent>of(myMemoryAndStorageLabel, myRamLabel, myVmHeapLabel, myInternalStorageLabel, mySdCardLabel,
myRamStorage, myVmHeapStorage, myInternalStorage, myExistingSdCard, myExternalRadioButton,
myNewSdCardStorage, myBuiltInRadioButton, myStorageSeparator);
}
private List<JComponent> getCameraComponents() {
return ImmutableList.<JComponent>of(myCameraLabel, myCameraSeparator, myBackCameraLabel,
myFrontCameraLabel, myBackCameraCombo, myFrontCameraCombo);
}
private List<JComponent> getNetworkComponents() {
return ImmutableList.<JComponent>of(myNetworkLabel, mySpeedLabel, mySpeedCombo, myLatencyCombo, myLatencyLabel, myNetworkSeparator);
}
/**
* Show or hide the "advanced" control panels.
*/
private void toggleAdvancedSettings(boolean show) {
for (JComponent c : myAdvancedOptionsComponents) {
c.setVisible(show);
}
validate();
myRoot.validate();
}
/**
* Enable/Disable controls based on the capabilities of the selected device. For example, some devices may
* not have a front facing camera.
*/
private void toggleOptionals(@NotNull Device device) {
myFrontCameraCombo.setEnabled(device.getDefaultHardware().getCamera(CameraLocation.FRONT) != null);
myBackCameraCombo.setEnabled(device.getDefaultHardware().getCamera(CameraLocation.BACK) != null);
myOrientationToggle.setEnabled(device.getDefaultState().getOrientation() != ScreenOrientation.SQUARE);
}
public static Storage calculateVmHeap(@NotNull Device device) {
// Set the default VM heap size. This is based on the Android CDD minimums for each
// screen size and density.
Screen s = device.getDefaultHardware().getScreen();
ScreenSize size = s.getSize();
Density density = s.getPixelDensity();
int vmHeapSize = 32;
if (size.equals(ScreenSize.XLARGE)) {
switch (density) {
case LOW:
case MEDIUM:
vmHeapSize = 32;
break;
case TV:
case HIGH:
case DPI_280:
case DPI_360:
vmHeapSize = 64;
break;
case XHIGH:
case DPI_400:
case XXHIGH:
case DPI_560:
case XXXHIGH:
vmHeapSize = 128;
break;
case NODPI:
case ANYDPI:
break;
}
}
else {
switch (density) {
case LOW:
case MEDIUM:
vmHeapSize = 16;
break;
case TV:
case HIGH:
case DPI_280:
case DPI_360:
vmHeapSize = 32;
break;
case XHIGH:
case DPI_400:
case XXHIGH:
case DPI_560:
case XXXHIGH:
vmHeapSize = 64;
break;
case NODPI:
case ANYDPI:
break;
}
}
return new Storage(vmHeapSize, Unit.MiB);
}
private abstract static class MemoryValueDeriver extends ValueDeriver<Storage> {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DEVICE_DEFINITION_KEY);
}
@Nullable
@Override
public Storage deriveValue(@NotNull ScopedStateStore state, @Nullable Key changedKey, @Nullable Storage currentValue) {
Device device = state.get(DEVICE_DEFINITION_KEY);
if (device != null) {
return getStorage(device);
}
else {
return null;
}
}
@Nullable
protected abstract Storage getStorage(@NotNull Device device);
}
}