blob: e9ac452876b75bf93c802f9226dc5c3742678ec5 [file] [log] [blame]
/*
* Copyright (C) 2013 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.configurations;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.State;
import com.android.tools.adtui.actions.DropDownAction;
import com.android.tools.idea.device.DeviceArtPainter;
import com.google.common.collect.ImmutableList;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import icons.StudioIcons;
import org.jetbrains.android.actions.RunAndroidAvdManagerAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import static com.android.ide.common.rendering.HardwareConfigHelper.*;
public class DeviceMenuAction extends DropDownAction {
private static final boolean LIST_RECENT_DEVICES = false;
private final ConfigurationHolder myRenderContext;
public DeviceMenuAction(@NotNull ConfigurationHolder renderContext) {
super("Device for Preview", "Device for Preview", StudioIcons.LayoutEditor.Toolbar.VIRTUAL_DEVICES);
myRenderContext = renderContext;
Presentation presentation = getTemplatePresentation();
updatePresentation(presentation);
}
@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
updatePresentation(e.getPresentation());
}
@Override
public boolean displayTextInToolbar() {
return true;
}
private void updatePresentation(Presentation presentation) {
Configuration configuration = myRenderContext.getConfiguration();
boolean visible = configuration != null;
if (visible) {
Device device = configuration.getCachedDevice();
String label = getDeviceLabel(device, true);
presentation.setText(label, false);
}
if (visible != presentation.isVisible()) {
presentation.setVisible(visible);
}
}
/**
* Returns a suitable label to use to display the given device
*
* @param device the device to produce a label for
* @param brief if true, generate a brief label (suitable for a toolbar
* button), otherwise a fuller name (suitable for a menu item)
* @return the label
*/
public static String getDeviceLabel(@Nullable Device device, boolean brief) {
if (device == null) {
return "";
}
String name = device.getDisplayName();
if (brief) {
// Produce a really brief summary of the device name, suitable for
// use in the narrow space available in the toolbar for example
int nexus = name.indexOf("Nexus"); //$NON-NLS-1$
if (nexus != -1) {
int begin = name.indexOf('(');
if (begin != -1) {
begin++;
int end = name.indexOf(')', begin);
if (end != -1) {
if (name.equals("Nexus 7 (2012)")) {
return "Nexus 7";
}
else {
return name.substring(begin, end).trim();
}
}
}
}
String skipPrefix = "Android ";
name = StringUtil.trimStart(name, skipPrefix);
}
return name;
}
private static Icon getDeviceClassIcon(@Nullable Device device) {
if (device != null) {
if (isWear(device)) {
return StudioIcons.LayoutEditor.Toolbar.DEVICE_WEAR;
}
else if (isTv(device)) {
return StudioIcons.LayoutEditor.Toolbar.DEVICE_TV;
}
else if (isAutomotive(device)) {
return StudioIcons.LayoutEditor.Toolbar.DEVICE_AUTOMOTIVE;
}
// Glass not yet in the device list
if (DeviceArtPainter.isTablet(device)) {
return StudioIcons.LayoutEditor.Toolbar.DEVICE_TABLET;
}
}
return StudioIcons.LayoutEditor.Toolbar.DEVICE_PHONE;
}
@Override
protected boolean updateActions(@NotNull DataContext context) {
removeAll();
Configuration configuration = myRenderContext.getConfiguration();
if (configuration == null) {
return true;
}
Device current = configuration.getCachedDevice();
ConfigurationManager configurationManager = configuration.getConfigurationManager();
if (LIST_RECENT_DEVICES) {
List<Device> recent = configurationManager.getDevices();
if (recent.size() > 1) {
for (Device device : recent) {
String label = getLabel(device, isNexus(device));
Icon icon = getDeviceClassIcon(device);
add(new SetDeviceAction(myRenderContext, label, device, icon, device == current));
}
addSeparator();
}
}
createDeviceMenuList(configuration, current);
return true;
}
private void createDeviceMenuList(@NotNull Configuration configuration, @Nullable Device currentDevice) {
Map<DeviceGroup, List<Device>> groupedDevices = DeviceUtils.getSuitableDevices(configuration);
// We don't add DeviceGroup.NEXUS because all Nexus devices with small screen size are legacy devices.
addDeviceSection(groupedDevices, DeviceGroup.NEXUS_XL, currentDevice);
addDeviceSection(groupedDevices, DeviceGroup.NEXUS_TABLET, currentDevice);
addDeviceSection(groupedDevices, DeviceGroup.WEAR, currentDevice);
addDeviceSection(groupedDevices, DeviceGroup.TV, currentDevice);
addDeviceSection(groupedDevices, DeviceGroup.AUTOMOTIVE, currentDevice);
addCustomDeviceSection(currentDevice);
addAvdDeviceSection(DeviceUtils.getAvdDevices(configuration), currentDevice);
addGenericDeviceSection(groupedDevices.getOrDefault(DeviceGroup.GENERIC, Collections.emptyList()), currentDevice);
add(ActionManager.getInstance().getAction(RunAndroidAvdManagerAction.ID));
}
private void addDeviceSection(@NotNull Map<DeviceGroup, List<Device>> groupedDevices,
@NotNull DeviceGroup group,
@Nullable Device current) {
List<Device> devices = groupedDevices.getOrDefault(group, Collections.emptyList());
if (!devices.isEmpty()) {
add(new DeviceCategory(getGroupTitle(group), null, getDeviceClassIcon(devices.get(0))));
for (final Device device : devices) {
String label = getLabel(device, isNexus(device));
add(new SetDeviceAction(myRenderContext, label, device, null, current == device));
}
addSeparator();
}
}
@NotNull
private static String getGroupTitle(@NotNull DeviceGroup group) {
switch (group) {
case NEXUS:
case NEXUS_XL:
return "Phone";
case NEXUS_TABLET:
return "Tablet";
case WEAR:
return "Wear";
case TV:
return "TV";
case AUTOMOTIVE:
return "Automotive";
case GENERIC:
return "Generic";
case OTHER:
return "Other";
default:
return "Device";
}
}
private void addCustomDeviceSection(@Nullable Device currentDevice) {
add(new SetCustomDeviceAction(myRenderContext, currentDevice));
addSeparator();
}
private void addAvdDeviceSection(@NotNull List<Device> devices, @Nullable Device current) {
if (!devices.isEmpty()) {
add(new DeviceCategory("Virtual Device", null, StudioIcons.LayoutEditor.Toolbar.VIRTUAL_DEVICES));
for (final Device device : devices) {
boolean selected = current != null && current.getId().equals(device.getId());
String avdDisplayName = "AVD: " + device.getDisplayName();
add(new SetAvdAction(myRenderContext, device, avdDisplayName, selected));
}
addSeparator();
}
}
private void addGenericDeviceSection(@NotNull List<Device> devices, @Nullable Device current) {
if (!devices.isEmpty()) {
DefaultActionGroup genericGroup = DefaultActionGroup.createPopupGroup(() -> "_Generic Phones and Tablets");
for (final Device device : devices) {
String label = getLabel(device, isNexus(device));
genericGroup.add(new SetDeviceAction(myRenderContext, label, device, null, current == device));
}
add(genericGroup);
}
}
@NotNull
public static ImmutableList<Device> getSortedDevicesInMenu(@NotNull Configuration configuration) {
Map<DeviceGroup, List<Device>> groupedDevices = DeviceUtils.getSuitableDevices(configuration);
// TODO: Refactor to have same device order as #createDeviceMenuList() function.
ImmutableList.Builder<Device> builder = new ImmutableList.Builder<>();
builder.addAll(groupedDevices.getOrDefault(DeviceGroup.NEXUS_XL, Collections.emptyList()));
builder.addAll(groupedDevices.getOrDefault(DeviceGroup.NEXUS_TABLET, Collections.emptyList()));
builder.addAll(groupedDevices.getOrDefault(DeviceGroup.WEAR, Collections.emptyList()));
builder.addAll(groupedDevices.getOrDefault(DeviceGroup.TV, Collections.emptyList()));
builder.addAll(groupedDevices.getOrDefault(DeviceGroup.AUTOMOTIVE, Collections.emptyList()));
builder.addAll(DeviceUtils.getAvdDevices(configuration));
builder.addAll(groupedDevices.getOrDefault(DeviceGroup.GENERIC, Collections.emptyList()));
return builder.build();
}
private String getLabel(Device device, boolean isNexus) {
// See if there is a better match, and if so, display it in the menu action
Configuration configuration = myRenderContext.getConfiguration();
if (configuration != null) {
VirtualFile better = ConfigurationMatcher.getBetterMatch(configuration, device, null, null, null);
if (better != null) {
return ConfigurationAction.getBetterMatchLabel(device.getDisplayName(), better, configuration.getFile());
}
}
return isNexus ? getNexusMenuLabel(device) : getGenericLabel(device);
}
private static final class DeviceCategory extends AnAction {
private Icon myIcon;
public DeviceCategory(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
super(text, description, null);
myIcon = icon;
}
@Override
public void update(@NotNull AnActionEvent e) {
Presentation p = e.getPresentation();
p.setEnabled(false);
p.setDisabledIcon(myIcon);
}
@Override
public void actionPerformed(AnActionEvent e) {
// Always disable, do nothing
}
}
protected abstract class DeviceAction extends ConfigurationAction {
DeviceAction(@NotNull ConfigurationHolder renderContext,
@Nullable String title) {
super(renderContext, title);
}
@Override
protected final void updatePresentation(@NotNull Presentation presentation) {
DeviceMenuAction.this.updatePresentation(presentation);
}
@Nullable
abstract public Device getDevice();
}
private class SetDeviceAction extends DeviceAction {
private final Device myDevice;
public SetDeviceAction(@NotNull ConfigurationHolder renderContext,
@NotNull final String title,
@NotNull final Device device,
@Nullable Icon defaultIcon,
final boolean select) {
super(renderContext, null);
myDevice = device;
// The name of AVD device may contain underline character, but they should not be recognized as the mnemonic.
getTemplatePresentation().setText(title, false);
if (select) {
getTemplatePresentation().putClientProperty(SELECTED_PROPERTY, true);
}
else if (ConfigurationAction.isBetterMatchLabel(title)) {
getTemplatePresentation().setIcon(ConfigurationAction.getBetterMatchIcon());
}
else if (defaultIcon != null) {
getTemplatePresentation().setIcon(defaultIcon);
}
}
@Override
protected void updateConfiguration(@NotNull Configuration configuration, boolean commit) {
// Attempt to jump to the default orientation of the new device; for example, if you're viewing a layout in
// portrait orientation on a Nexus 4 (its default), and you switch to a Nexus 10, we jump to landscape orientation
// (its default) unless of course there is a different layout that is the best fit for that device.
Device prevDevice = configuration.getCachedDevice();
State prevState = configuration.getDeviceState();
String newState = prevState != null ? prevState.getName() : null;
if (prevDevice != null && prevState != null && prevState.isDefaultState() &&
!myDevice.getDefaultState().getName().equals(prevState.getName()) &&
configuration.getEditedConfig().getScreenOrientationQualifier() == null) {
VirtualFile file = configuration.getFile();
if (file != null) {
String name = myDevice.getDefaultState().getName();
if (ConfigurationMatcher.getBetterMatch(configuration, myDevice, name, null, null) == null) {
newState = name;
}
}
}
if (newState != null) {
configuration.setDeviceStateName(newState);
}
if (commit) {
configuration.getConfigurationManager().selectDevice(myDevice);
}
configuration.setDevice(myDevice, true);
}
@NotNull
@Override
public Device getDevice() {
return myDevice;
}
}
private class SetCustomDeviceAction extends DeviceAction {
private static final String CUSTOM_DEVICE_NAME = "Custom";
@Nullable private final Device myDevice;
@Nullable private Device myCustomDevice;
public SetCustomDeviceAction(@NotNull ConfigurationHolder renderContext, @Nullable Device device) {
super(renderContext, CUSTOM_DEVICE_NAME);
myDevice = device;
if (myDevice != null && Configuration.CUSTOM_DEVICE_ID.equals(myDevice.getId())) {
getTemplatePresentation().putClientProperty(SELECTED_PROPERTY, true);
}
}
@Override
protected void updateConfiguration(@NotNull Configuration configuration, boolean commit) {
if (myDevice != null) {
Device.Builder customBuilder = new Device.Builder(myDevice);
customBuilder.setTagId(myDevice.getTagId());
customBuilder.setName(CUSTOM_DEVICE_NAME);
customBuilder.setId(Configuration.CUSTOM_DEVICE_ID);
myCustomDevice = customBuilder.build();
configuration.setEffectiveDevice(myCustomDevice, myDevice.getDefaultState());
}
}
@Nullable
@Override
public Device getDevice() {
return myCustomDevice;
}
}
private class SetAvdAction extends ConfigurationAction {
@NotNull private final Device myAvdDevice;
public SetAvdAction(@NotNull ConfigurationHolder renderContext,
@NotNull Device avdDevice,
@NotNull String displayName,
final boolean select) {
super(renderContext, displayName);
myAvdDevice = avdDevice;
getTemplatePresentation().putClientProperty(SELECTED_PROPERTY, select);
}
@Override
protected void updatePresentation(@NotNull Presentation presentation) {
DeviceMenuAction.this.updatePresentation(presentation);
}
@Override
protected void updateConfiguration(@NotNull Configuration configuration, boolean commit) {
configuration.setEffectiveDevice(myAvdDevice, myAvdDevice.getDefaultState());
}
}
}