blob: 956ac18399a935eecf0d8422174f91ea6d9584c6 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.internal.editors.layout.configuration;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_THEME;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LocaleQualifier;
import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.NightMode;
import com.android.resources.ResourceFolderType;
import com.android.resources.ScreenSize;
import com.android.resources.UiMode;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.State;
import com.google.common.base.Splitter;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.QualifiedName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.Collection;
import java.util.List;
/** A description of a configuration, used for persistence */
public class ConfigurationDescription {
private static final String TAG_PREVIEWS = "previews"; //$NON-NLS-1$
private static final String TAG_PREVIEW = "preview"; //$NON-NLS-1$
private static final String ATTR_TARGET = "target"; //$NON-NLS-1$
private static final String ATTR_CONFIG = "config"; //$NON-NLS-1$
private static final String ATTR_LOCALE = "locale"; //$NON-NLS-1$
private static final String ATTR_ACTIVITY = "activity"; //$NON-NLS-1$
private static final String ATTR_DEVICE = "device"; //$NON-NLS-1$
private static final String ATTR_STATE = "devicestate"; //$NON-NLS-1$
private static final String ATTR_UIMODE = "ui"; //$NON-NLS-1$
private static final String ATTR_NIGHTMODE = "night"; //$NON-NLS-1$
private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
/**
* Settings name for file-specific configuration preferences, such as which theme or
* device to render the current layout with
*/
public final static QualifiedName NAME_CONFIG_STATE =
new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
/** The project corresponding to this configuration's description */
public final IProject project;
/** The display name */
public String displayName;
/** The theme */
public String theme;
/** The target */
public IAndroidTarget target;
/** The display name */
public FolderConfiguration folder;
/** The locale */
public Locale locale = Locale.ANY;
/** The device */
public Device device;
/** The device state */
public State state;
/** The activity */
public String activity;
/** UI mode */
@NonNull
public UiMode uiMode = UiMode.NORMAL;
/** Night mode */
@NonNull
public NightMode nightMode = NightMode.NOTNIGHT;
private ConfigurationDescription(@Nullable IProject project) {
this.project = project;
}
/**
* Returns the persistent configuration description from the given file
*
* @param file the file to look up a description from
* @return the description or null if never written
*/
@Nullable
public static String getDescription(@NonNull IFile file) {
return AdtPlugin.getFileProperty(file, NAME_CONFIG_STATE);
}
/**
* Sets the persistent configuration description data for the given file
*
* @param file the file to associate the description with
* @param description the description
*/
public static void setDescription(@NonNull IFile file, @NonNull String description) {
AdtPlugin.setFileProperty(file, NAME_CONFIG_STATE, description);
}
/**
* Creates a description from a given configuration
*
* @param project the project for this configuration's description
* @param configuration the configuration to describe
* @return a new configuration
*/
public static ConfigurationDescription fromConfiguration(
@Nullable IProject project,
@NonNull Configuration configuration) {
ConfigurationDescription description = new ConfigurationDescription(project);
description.displayName = configuration.getDisplayName();
description.theme = configuration.getTheme();
description.target = configuration.getTarget();
description.folder = new FolderConfiguration();
description.folder.set(configuration.getFullConfig());
description.locale = configuration.getLocale();
description.device = configuration.getDevice();
description.state = configuration.getDeviceState();
description.activity = configuration.getActivity();
return description;
}
/**
* Initializes a string previously created with
* {@link #toXml(Document)}
*
* @param project the project for this configuration's description
* @param element the element to read back from
* @param deviceList list of available devices
* @return true if the configuration was initialized
*/
@Nullable
public static ConfigurationDescription fromXml(
@Nullable IProject project,
@NonNull Element element,
@NonNull Collection<Device> deviceList) {
ConfigurationDescription description = new ConfigurationDescription(project);
if (!TAG_PREVIEW.equals(element.getTagName())) {
return null;
}
String displayName = element.getAttribute(ATTR_NAME);
if (!displayName.isEmpty()) {
description.displayName = displayName;
}
String config = element.getAttribute(ATTR_CONFIG);
Iterable<String> segments = Splitter.on('-').split(config);
description.folder = FolderConfiguration.getConfig(segments);
String theme = element.getAttribute(ATTR_THEME);
if (!theme.isEmpty()) {
description.theme = theme;
}
String targetId = element.getAttribute(ATTR_TARGET);
if (!targetId.isEmpty()) {
IAndroidTarget target = Configuration.stringToTarget(targetId);
description.target = target;
}
String localeString = element.getAttribute(ATTR_LOCALE);
if (!localeString.isEmpty()) {
// Load locale. Note that this can get overwritten by the
// project-wide settings read below.
String locales[] = localeString.split(SEP_LOCALE);
if (locales[0].length() > 0 && !LocaleQualifier.FAKE_VALUE.equals(locales[0])) {
String language = locales[0];
if (locales.length >= 2 && locales[1].length() > 0 && !LocaleQualifier.FAKE_VALUE.equals(locales[1])) {
description.locale = Locale.create(LocaleQualifier.getQualifier(language + "-r" + locales[1]));
} else {
description.locale = Locale.create(new LocaleQualifier(language));
}
} else {
description.locale = Locale.ANY;
}
}
String activity = element.getAttribute(ATTR_ACTIVITY);
if (activity.isEmpty()) {
activity = null;
}
String deviceString = element.getAttribute(ATTR_DEVICE);
if (!deviceString.isEmpty()) {
for (Device d : deviceList) {
if (d.getName().equals(deviceString)) {
description.device = d;
String stateName = element.getAttribute(ATTR_STATE);
if (stateName.isEmpty() || stateName.equals("null")) {
description.state = Configuration.getState(d, stateName);
} else if (d.getAllStates().size() > 0) {
description.state = d.getAllStates().get(0);
}
break;
}
}
}
String uiModeString = element.getAttribute(ATTR_UIMODE);
if (!uiModeString.isEmpty()) {
description.uiMode = UiMode.getEnum(uiModeString);
if (description.uiMode == null) {
description.uiMode = UiMode.NORMAL;
}
}
String nightModeString = element.getAttribute(ATTR_NIGHTMODE);
if (!nightModeString.isEmpty()) {
description.nightMode = NightMode.getEnum(nightModeString);
if (description.nightMode == null) {
description.nightMode = NightMode.NOTNIGHT;
}
}
// Should I really be storing the FULL configuration? Might be trouble if
// you bring a different device
return description;
}
/**
* Write this description into the given document as a new element.
*
* @param document the document to add the description to
* @return the newly inserted element
*/
@NonNull
public Element toXml(Document document) {
Element element = document.createElement(TAG_PREVIEW);
element.setAttribute(ATTR_NAME, displayName);
FolderConfiguration fullConfig = folder;
String folderName = fullConfig.getFolderName(ResourceFolderType.LAYOUT);
element.setAttribute(ATTR_CONFIG, folderName);
if (theme != null) {
element.setAttribute(ATTR_THEME, theme);
}
if (target != null) {
element.setAttribute(ATTR_TARGET, Configuration.targetToString(target));
}
if (locale != null && (locale.hasLanguage() || locale.hasRegion())) {
String value;
if (locale.hasRegion()) {
value = locale.qualifier.getLanguage() + SEP_LOCALE + locale.qualifier.getRegion();
} else {
value = locale.qualifier.getLanguage();
}
element.setAttribute(ATTR_LOCALE, value);
}
if (device != null) {
element.setAttribute(ATTR_DEVICE, device.getName());
if (state != null) {
element.setAttribute(ATTR_STATE, state.getName());
}
}
if (activity != null) {
element.setAttribute(ATTR_ACTIVITY, activity);
}
if (uiMode != null && uiMode != UiMode.NORMAL) {
element.setAttribute(ATTR_UIMODE, uiMode.getResourceValue());
}
if (nightMode != null && nightMode != NightMode.NOTNIGHT) {
element.setAttribute(ATTR_NIGHTMODE, nightMode.getResourceValue());
}
Element parent = document.getDocumentElement();
if (parent == null) {
parent = document.createElement(TAG_PREVIEWS);
document.appendChild(parent);
}
parent.appendChild(element);
return element;
}
/** Returns the preferred theme, or null */
@Nullable
String computePreferredTheme() {
if (project == null) {
return "Theme";
}
ManifestInfo manifest = ManifestInfo.get(project);
// Look up the screen size for the current state
ScreenSize screenSize = null;
if (device != null) {
List<State> states = device.getAllStates();
for (State s : states) {
FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s);
if (folderConfig != null) {
ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
screenSize = qualifier.getValue();
break;
}
}
}
// Look up the default/fallback theme to use for this project (which
// depends on the screen size when no particular theme is specified
// in the manifest)
String defaultTheme = manifest.getDefaultTheme(target, screenSize);
String preferred = defaultTheme;
if (theme == null) {
// If we are rendering a layout in included context, pick the theme
// from the outer layout instead
if (activity != null) {
ActivityAttributes attributes = manifest.getActivityAttributes(activity);
if (attributes != null) {
preferred = attributes.getTheme();
}
}
if (preferred == null) {
preferred = defaultTheme;
}
theme = preferred;
}
return preferred;
}
private void checkThemePrefix() {
if (theme != null && !theme.startsWith(PREFIX_RESOURCE_REF)) {
if (theme.isEmpty()) {
computePreferredTheme();
return;
}
if (target != null) {
Sdk sdk = Sdk.getCurrent();
if (sdk != null) {
AndroidTargetData data = sdk.getTargetData(target);
if (data != null) {
ResourceRepository resources = data.getFrameworkResources();
if (resources != null
&& resources.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) {
theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
return;
}
}
}
}
theme = STYLE_RESOURCE_PREFIX + theme;
}
}
}