/*
* 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.Density;
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.tools.idea.wizard.*;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.EnumComboBoxModel;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBList;
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.io.File;
import java.util.*;
import static com.android.sdklib.devices.Storage.Unit;
import static com.android.tools.idea.avdmanager.AvdWizardConstants.*;
import static com.android.tools.idea.wizard.ScopedStateStore.Key;
import static com.android.tools.idea.wizard.ScopedStateStore.createKey;
/**
* 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 DynamicWizardStepWithHeaderAndDescription {
// CardLayout ids for switching between SD card types
private static final String EXISTING_SDCARD = "existingsdcard";
private static final String NEW_SDCARD = "newsdcard";
private JBLabel myDeviceName;
private JBLabel myDeviceDetails;
private JButton myChangeDeviceButton;
private JBLabel mySystemImageName;
private JBLabel mySystemImageDetails;
private JButton myChangeSystemImageButton;
private TextFieldWithBrowseButton myExistingSdCard;
private JPanel mySdCardSettings;
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 AvdConfigurationOptionHelpPanel myAvdConfigurationOptionHelpPanel;
private JBLabel myToggleSdCardSettingsLabel;
private JPanel myMemoryAndStoragePanel;
private JPanel myStartupOptionsPanel;
private JPanel myNetworkPanel;
private JPanel myCameraPanel;
private JPanel myPerformancePanel;
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 JPanel mySkinPanel;
private TextFieldWithBrowseButton myCustomSkinPath;
private HyperlinkLabel myHardwareSkinHelpLabel;
private Set<JComponent> myAdvancedOptionsComponents;
// 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;
// Labels used for the SD card-type toggle link
private static final String SWITCH_TO_NEW_SD_CARD = "Or create a new image...";
private static final String SWITCH_TO_EXISTING_SD_CARD = "Or use an existing data file...";
private Set<JComponent> myErrorStateComponents = Sets.newHashSet();
// Intermediate key for storing the string path before we convert it to a file
private static final Key<String> CUSTOM_SKIN_PATH_KEY = createKey(WIZARD_ONLY + "CustomSkinPath",
ScopedStateStore.Scope.STEP, String.class);
public ConfigureAvdOptionsStep(@Nullable Disposable parentDisposable) {
super("Configure AVD", null, null, parentDisposable);
myAvdConfigurationOptionHelpPanel.setPreferredSize(new Dimension(360, -1));
setBodyComponent(myRoot);
registerAdvancedOptionsVisibility();
toggleAdvancedSettings(false);
myToggleSdCardSettingsLabel.setText(SWITCH_TO_EXISTING_SD_CARD);
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(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DynamicWizard wizard = getWizard();
if (wizard != null) {
wizard.navigateToNamedStep(CHOOSE_DEVICE_DEFINITION_STEP, false);
}
}
});
myChangeSystemImageButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DynamicWizard wizard = getWizard();
if (wizard != null) {
wizard.navigateToNamedStep(CHOOSE_SYSTEM_IMAGE_STEP, false);
}
}
});
myToggleSdCardSettingsLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
toggleSdCardSettings(SWITCH_TO_EXISTING_SD_CARD.equals(myToggleSdCardSettingsLabel.getText()));
}
});
myToggleSdCardSettingsLabel.setForeground(JBColor.blue);
}
/**
* Toggle the SD card between using an existing file and creating a new file.
*/
private void toggleSdCardSettings(boolean useExisting) {
if (useExisting) {
((CardLayout)mySdCardSettings.getLayout()).show(mySdCardSettings, EXISTING_SDCARD);
myToggleSdCardSettingsLabel.setText(SWITCH_TO_NEW_SD_CARD);
myState.put(USE_EXISTING_SD_CARD, true);
} else {
((CardLayout)mySdCardSettings.getLayout()).show(mySdCardSettings, NEW_SDCARD);
myToggleSdCardSettingsLabel.setText(SWITCH_TO_EXISTING_SD_CARD);
myState.put(USE_EXISTING_SD_CARD, false);
}
}
@Override
public void init() {
super.init();
registerComponents();
deregister(getDescriptionText());
getDescriptionText().setVisible(false);
}
@Override
public void onEnterStep() {
super.onEnterStep();
Device device = myState.get(DEVICE_DEFINITION_KEY);
if (device != null) {
toggleOptionals(device);
}
Boolean useExisting = myState.get(USE_EXISTING_SD_CARD);
if (useExisting != null)
toggleSdCardSettings(useExisting);
}
@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(USE_EXISTING_SD_CARD);
if (useExistingSd != null && useExistingSd) {
String path = myState.get(EXISTING_SD_LOCATION);
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(SD_CARD_STORAGE_KEY);
if (sdCard != null && (sdCard.getSizeAsUnit(Unit.MiB) < 30 || sdCard.getSizeAsUnit(Unit.GiB) > 1023)) {
setErrorState("The SD card must be between 30Mb and 1023Gb",
myMemoryAndStorageLabel, mySdCardLabel, myNewSdCardStorage);
valid = false;
}
}
File skinDir = myState.get(CUSTOM_SKIN_FILE_KEY);
if (skinDir != null) {
File layoutFile = new File(skinDir, "layout");
if (!layoutFile.isFile()) {
setErrorHtml("The skin directory does not point to a valid skin.");
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 {
c.setBorder(null);
}
}
myAvdConfigurationOptionHelpPanel.setErrorMessage("");
}
/**
* Set an error message and mark the given components as being in error state
*/
private void setErrorState(String message, JComponent... errorComponents) {
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 {
c.setBorder(new LineBorder(JBColor.RED));
}
myErrorStateComponents.add(c);
}
}
/**
* Bind components to their specified keys and help messaging.
*/
private void registerComponents() {
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(SD_CARD_STORAGE_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 device.getDefaultHardware().getRam();
}
});
registerValueDeriver(VM_HEAP_STORAGE_KEY, new MemoryValueDeriver() {
@Nullable
@Override
protected Storage getStorage(@NotNull Device device) {
return calculateVmHeap(device);
}
});
}
registerValueDeriver(DEFAULT_ORIENTATION_KEY, new ValueDeriver<ScreenOrientation>() {
@Nullable
@Override
public Set<Key<?>> getTriggerKeys() {
return makeSetOf(DEVICE_DEFINITION_KEY);
}
@Nullable
@Override
public ScreenOrientation deriveValue(@NotNull ScopedStateStore state,
@Nullable Key changedKey,
@Nullable ScreenOrientation currentValue) {
Device device = state.get(DEVICE_DEFINITION_KEY);
if (device != null) {
return device.getDefaultState().getOrientation();
} else {
return null;
}
}
});
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(EXISTING_SD_LOCATION, 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);
}
});
File currentSkinFile = myState.get(CUSTOM_SKIN_FILE_KEY);
if (currentSkinFile != null) {
myState.put(CUSTOM_SKIN_PATH_KEY, currentSkinFile.getPath());
}
register(CUSTOM_SKIN_PATH_KEY, myCustomSkinPath);
FileChooserDescriptor skinChooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false);
myCustomSkinPath.addBrowseFolderListener("Select Custom Skin", "Select the directory containing your custom skin definition",
getProject(), skinChooserDescriptor);
setControlDescription(myCustomSkinPath, myAvdConfigurationOptionHelpPanel.getDescription(CUSTOM_SKIN_FILE_KEY));
registerValueDeriver(CUSTOM_SKIN_FILE_KEY, new ValueDeriver<File>() {
@Nullable
@Override
public Set<ScopedStateStore.Key<?>> getTriggerKeys() {
return makeSetOf(CUSTOM_SKIN_PATH_KEY);
}
@Nullable
@Override
public File deriveValue(@NotNull ScopedStateStore state, @Nullable ScopedStateStore.Key changedKey, @Nullable File currentValue) {
String path = state.get(CUSTOM_SKIN_PATH_KEY);
if (path != null) {
File file = new File(path);
if (file.isDirectory()) {
return file;
}
}
return null;
}
});
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 ChooseModuleTypeStep.iconToImage(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("");
}
@NotNull
@Override
public String getStepName() {
return "Configure AVD Options";
}
@Override
public JComponent getPreferredFocusedComponent() {
return null;
}
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.getIcon(String.format("/icons/versions/%s_32.png", codeName), AndroidIcons.class);
component.setIcon(icon);
} catch (RuntimeException e) {
// Pass
}
}
}
};
private static 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.target.getFullName() + " " + newValue.systemImage.getAbiType());
}
}
};
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() {
myAdvancedOptionsComponents = ImmutableSet.<JComponent>of(myMemoryAndStoragePanel, myCameraPanel, myNetworkPanel,
mySkinPanel);
}
/**
* Show or hide the "advanced" control panels.
*/
private void toggleAdvancedSettings(boolean show) {
for (JComponent c : myAdvancedOptionsComponents) {
c.setVisible(show);
}
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);
}
private 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:
vmHeapSize = 64;
break;
case XHIGH:
case XXHIGH:
case XXXHIGH:
vmHeapSize = 128;
break;
case NODPI:
break;
}
} else {
switch (density) {
case LOW:
case MEDIUM:
vmHeapSize = 16;
break;
case TV:
case HIGH:
vmHeapSize = 32;
break;
case XHIGH:
case XXHIGH:
case XXXHIGH:
vmHeapSize = 64;
break;
case NODPI:
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);
}
}