blob: 2d9171d3490c797956e1df8932e63b535a4b9306 [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.annotations.Nullable;
import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.State;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.tools.idea.ddms.screenshot.DeviceArtPainter;
import com.android.tools.idea.npw.FormFactorUtils;
import com.android.tools.idea.rendering.RenderService;
import com.android.tools.idea.rendering.multi.RenderPreviewMode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.vfs.VirtualFile;
import icons.AndroidIcons;
import org.jetbrains.android.actions.RunAndroidAvdManagerAction;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.*;
import static com.android.ide.common.rendering.HardwareConfigHelper.*;
public class DeviceMenuAction extends FlatComboAction {
private static final boolean LIST_RECENT_DEVICES = false;
private final RenderContext myRenderContext;
private final boolean myClassicStyle;
public DeviceMenuAction(@NotNull RenderContext renderContext) {
this(renderContext, !RenderService.NELE_ENABLED);
}
public DeviceMenuAction(@NotNull RenderContext renderContext, boolean classicStyle) {
myRenderContext = renderContext;
myClassicStyle = classicStyle;
Presentation presentation = getTemplatePresentation();
presentation.setDescription("The virtual device to render the layout with");
if (classicStyle) {
presentation.setIcon(AndroidIcons.Display);
}
updatePresentation(presentation);
}
@Override
public void update(AnActionEvent e) {
super.update(e);
updatePresentation(e.getPresentation());
}
private void updatePresentation(Presentation presentation) {
Configuration configuration = myRenderContext.getConfiguration();
boolean visible = configuration != null;
if (visible) {
Device device = configuration.getDevice();
String label = getDeviceLabel(device, true);
presentation.setText(label);
if (!myClassicStyle) {
presentation.setIcon(getDeviceClassIcon(device));
}
}
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 ";
if (name.startsWith(skipPrefix)) {
name = name.substring(skipPrefix.length());
}
}
return name;
}
/**
* Similar to {@link DeviceMenuAction.FormFactor#getFormFactor(Device)}
* but (a) distinguishes between tablets and phones, and (b) uses the new Nele icons
*/
public Icon getDeviceClassIcon(@Nullable Device device) {
if (myClassicStyle) {
FormFactor formFactor = device != null ? FormFactor.getFormFactor(device) : FormFactor.MOBILE;
return formFactor.getIcon();
}
if (device != null) {
if (HardwareConfigHelper.isWear(device)) {
return AndroidIcons.NeleIcons.Wear;
}
else if (HardwareConfigHelper.isTv(device)) {
return AndroidIcons.NeleIcons.Tv;
}
// Glass, Car not yet in the device list
if (DeviceArtPainter.isTablet(device)) {
return AndroidIcons.NeleIcons.Tablet;
}
}
return AndroidIcons.NeleIcons.Phone;
}
/** TODO: Combine with {@link FormFactorUtils.FormFactor} */
public enum FormFactor {
MOBILE, WEAR, GLASS, TV, CAR;
private Icon myIcon64;
public static FormFactor getFormFactor(@NotNull Device device) {
if (HardwareConfigHelper.isWear(device)) {
return WEAR;
} else if (HardwareConfigHelper.isTv(device)) {
return TV;
}
// Glass, Car not yet in the device list
return MOBILE;
}
@NotNull
public Icon getIcon() {
switch (this) {
case CAR: return AndroidIcons.FormFactors.Car_16;
case WEAR: return AndroidIcons.FormFactors.Wear_16;
case TV: return AndroidIcons.FormFactors.Tv_16;
case GLASS: return AndroidIcons.FormFactors.Glass_16;
case MOBILE:
default:
return AndroidIcons.FormFactors.Mobile_16;
}
}
@NotNull
public Icon getLargeIcon() {
switch (this) {
case CAR: return AndroidIcons.FormFactors.Car_128;
case WEAR: return AndroidIcons.FormFactors.Wear_128;
case TV: return AndroidIcons.FormFactors.Tv_128;
case GLASS: return AndroidIcons.FormFactors.Glass_128;
case MOBILE:
default:
return AndroidIcons.FormFactors.Mobile_128;
}
}
public boolean hasEmulator() {
return this != GLASS;
}
public Icon getIcon64() {
switch (this) {
case CAR: return AndroidIcons.FormFactors.Car_64;
case WEAR: return AndroidIcons.FormFactors.Wear_64;
case TV: return AndroidIcons.FormFactors.Tv_64;
case GLASS: return AndroidIcons.FormFactors.Glass_64;
case MOBILE:
default:
return AndroidIcons.FormFactors.Mobile_64;
}
}
}
@Override
@NotNull
protected DefaultActionGroup createPopupActionGroup(JComponent button) {
DefaultActionGroup group = new DefaultActionGroup(null, true);
Configuration configuration = myRenderContext.getConfiguration();
if (configuration == null) {
return group;
}
Device current = configuration.getDevice();
ConfigurationManager configurationManager = configuration.getConfigurationManager();
List<Device> deviceList = configurationManager.getDevices();
if (LIST_RECENT_DEVICES) {
List<Device> recent = configurationManager.getDevices();
if (recent.size() > 1) {
boolean separatorNeeded = false;
for (Device device : recent) {
String label = getLabel(device, isNexus(device));
Icon icon = getDeviceClassIcon(device);
group.add(new SetDeviceAction(myRenderContext, label, device, icon, device == current));
separatorNeeded = true;
}
if (separatorNeeded) {
group.addSeparator();
}
}
}
AndroidFacet facet = AndroidFacet.getInstance(configurationManager.getModule());
if (facet == null) {
// Unlikely, but has happened - see http://b.android.com/68091
return group;
}
final AvdManager avdManager = facet.getAvdManagerSilently();
if (avdManager != null) {
boolean separatorNeeded = false;
for (AvdInfo avd : avdManager.getValidAvds()) {
Device device = configurationManager.createDeviceForAvd(avd);
if (device != null) {
String avdName = "AVD: " + avd.getName();
boolean selected = current != null && (current.getDisplayName().equals(avdName) || current.getId().equals(avdName));
Icon icon = getDeviceClassIcon(device);
group.add(new SetDeviceAction(myRenderContext, avdName, device, icon, selected));
separatorNeeded = true;
}
}
if (separatorNeeded) {
group.addSeparator();
}
}
if (!deviceList.isEmpty()) {
Map<String, List<Device>> manufacturers = new TreeMap<String, List<Device>>();
for (Device device : deviceList) {
List<Device> devices;
if (manufacturers.containsKey(device.getManufacturer())) {
devices = manufacturers.get(device.getManufacturer());
}
else {
devices = new ArrayList<Device>();
manufacturers.put(device.getManufacturer(), devices);
}
devices.add(device);
}
List<Device> nexus = new ArrayList<Device>();
Map<FormFactor,List<Device>> deviceMap = Maps.newEnumMap(FormFactor.class);
for (FormFactor factor : FormFactor.values()) {
deviceMap.put(factor, Lists.<Device>newArrayList());
}
for (List<Device> devices : manufacturers.values()) {
for (Device device : devices) {
if (isNexus(device) && !device.getManufacturer().equals(MANUFACTURER_GENERIC)) {
nexus.add(device);
} else {
deviceMap.get(FormFactor.getFormFactor(device)).add(device);
}
}
}
sortNexusList(nexus);
addNexusDeviceSection(group, current, nexus);
group.addSeparator();
addDeviceSection(group, current, deviceMap, false, FormFactor.WEAR);
group.addSeparator();
addDeviceSection(group, current, deviceMap, false, FormFactor.TV);
group.addSeparator();
DefaultActionGroup genericGroup = new DefaultActionGroup("_Generic Phones and Tablets", true);
addDeviceSection(genericGroup, current, deviceMap, true, FormFactor.MOBILE);
group.add(genericGroup);
}
group.add(new RunAndroidAvdManagerAction("Add Device Definition..."));
group.addSeparator();
if (RenderPreviewMode.getCurrent() != RenderPreviewMode.SCREENS) {
ConfigurationMenuAction.addScreenSizeAction(myRenderContext, group);
} else {
ConfigurationMenuAction.addRemovePreviewsAction(myRenderContext, group);
}
return group;
}
private void addNexusDeviceSection(@NotNull DefaultActionGroup group, @Nullable Device current, @NotNull List<Device> devices) {
for (final Device device : devices) {
String label = getLabel(device, true /*nexus*/);
Icon icon = getDeviceClassIcon(device);
group.add(new SetDeviceAction(myRenderContext, label, device, icon, current == device));
}
}
private void addDeviceSection(@NotNull DefaultActionGroup group,
@Nullable Device current,
@NotNull Map<FormFactor, List<Device>> deviceMap,
boolean reverse,
@NotNull FormFactor factor) {
List<Device> generic = deviceMap.get(factor);
if (reverse) {
Collections.reverse(generic);
}
for (final Device device : generic) {
String label = getLabel(device, false /*nexus*/);
Icon icon = getDeviceClassIcon(device);
group.add(new SetDeviceAction(myRenderContext, label, device, icon, current == device));
}
}
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 ? getNexusLabel(device) : getGenericLabel(device);
}
private class SetDeviceAction extends ConfigurationAction {
private final Device myDevice;
public SetDeviceAction(@NotNull RenderContext renderContext,
@NotNull final String title,
@NotNull final Device device,
@Nullable Icon defaultIcon,
final boolean select) {
super(renderContext, title);
myDevice = device;
if (select) {
getTemplatePresentation().setIcon(AllIcons.Actions.Checked);
} else if (ConfigurationAction.isBetterMatchLabel(title)) {
getTemplatePresentation().setIcon(ConfigurationAction.getBetterMatchIcon());
} else if (defaultIcon != null) {
getTemplatePresentation().setIcon(defaultIcon);
}
}
@Override
protected void updatePresentation() {
DeviceMenuAction.this.updatePresentation(DeviceMenuAction.this.getTemplatePresentation());
}
@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.getDevice();
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);
} else {
configuration.setDevice(myDevice, true);
}
}
}
}