| /* |
| * 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; |
| } |
| } |
| } |