Home | History | Annotate | Download | only in configuration
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
     18 
     19 import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
     20 import static com.android.SdkConstants.ATTR_NAME;
     21 import static com.android.SdkConstants.ATTR_THEME;
     22 import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
     23 import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
     24 
     25 import com.android.annotations.NonNull;
     26 import com.android.annotations.Nullable;
     27 import com.android.ide.common.resources.ResourceRepository;
     28 import com.android.ide.common.resources.configuration.DeviceConfigHelper;
     29 import com.android.ide.common.resources.configuration.FolderConfiguration;
     30 import com.android.ide.common.resources.configuration.LanguageQualifier;
     31 import com.android.ide.common.resources.configuration.RegionQualifier;
     32 import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
     33 import com.android.ide.eclipse.adt.AdtPlugin;
     34 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     35 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     36 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     37 import com.android.resources.NightMode;
     38 import com.android.resources.ResourceFolderType;
     39 import com.android.resources.ScreenSize;
     40 import com.android.resources.UiMode;
     41 import com.android.sdklib.IAndroidTarget;
     42 import com.android.sdklib.devices.Device;
     43 import com.android.sdklib.devices.State;
     44 import com.google.common.base.Splitter;
     45 
     46 import org.eclipse.core.resources.IFile;
     47 import org.eclipse.core.resources.IProject;
     48 import org.eclipse.core.runtime.QualifiedName;
     49 import org.w3c.dom.Document;
     50 import org.w3c.dom.Element;
     51 
     52 import java.util.List;
     53 import java.util.Map;
     54 
     55 /** A description of a configuration, used for persistence */
     56 public class ConfigurationDescription {
     57     private static final String TAG_PREVIEWS = "previews";    //$NON-NLS-1$
     58     private static final String TAG_PREVIEW = "preview";      //$NON-NLS-1$
     59     private static final String ATTR_TARGET = "target";       //$NON-NLS-1$
     60     private static final String ATTR_CONFIG = "config";       //$NON-NLS-1$
     61     private static final String ATTR_LOCALE = "locale";       //$NON-NLS-1$
     62     private static final String ATTR_ACTIVITY = "activity";   //$NON-NLS-1$
     63     private static final String ATTR_DEVICE = "device";       //$NON-NLS-1$
     64     private static final String ATTR_STATE = "devicestate";   //$NON-NLS-1$
     65     private static final String ATTR_UIMODE = "ui";           //$NON-NLS-1$
     66     private static final String ATTR_NIGHTMODE = "night";     //$NON-NLS-1$
     67     private final static String SEP_LOCALE = "-";             //$NON-NLS-1$
     68 
     69     /**
     70      * Settings name for file-specific configuration preferences, such as which theme or
     71      * device to render the current layout with
     72      */
     73     public final static QualifiedName NAME_CONFIG_STATE =
     74         new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
     75 
     76     /** The project corresponding to this configuration's description */
     77     public final IProject project;
     78 
     79     /** The display name */
     80     public String displayName;
     81 
     82     /** The theme */
     83     public String theme;
     84 
     85     /** The target */
     86     public IAndroidTarget target;
     87 
     88     /** The display name */
     89     public FolderConfiguration folder;
     90 
     91     /** The locale */
     92     public Locale locale = Locale.ANY;
     93 
     94     /** The device */
     95     public Device device;
     96 
     97     /** The device state */
     98     public State state;
     99 
    100     /** The activity */
    101     public String activity;
    102 
    103     /** UI mode */
    104     @NonNull
    105     public UiMode uiMode = UiMode.NORMAL;
    106 
    107     /** Night mode */
    108     @NonNull
    109     public NightMode nightMode = NightMode.NOTNIGHT;
    110 
    111     private ConfigurationDescription(@Nullable IProject project) {
    112         this.project = project;
    113     }
    114 
    115     /**
    116      * Returns the persistent configuration description from the given file
    117      *
    118      * @param file the file to look up a description from
    119      * @return the description or null if never written
    120      */
    121     @Nullable
    122     public static String getDescription(@NonNull IFile file) {
    123         return AdtPlugin.getFileProperty(file, NAME_CONFIG_STATE);
    124     }
    125 
    126     /**
    127      * Sets the persistent configuration description data for the given file
    128      *
    129      * @param file the file to associate the description with
    130      * @param description the description
    131      */
    132     public static void setDescription(@NonNull IFile file, @NonNull String description) {
    133         AdtPlugin.setFileProperty(file, NAME_CONFIG_STATE, description);
    134     }
    135 
    136     /**
    137      * Creates a description from a given configuration
    138      *
    139      * @param project the project for this configuration's description
    140      * @param configuration the configuration to describe
    141      * @return a new configuration
    142      */
    143     public static ConfigurationDescription fromConfiguration(
    144             @Nullable IProject project,
    145             @NonNull Configuration configuration) {
    146         ConfigurationDescription description = new ConfigurationDescription(project);
    147         description.displayName = configuration.getDisplayName();
    148         description.theme = configuration.getTheme();
    149         description.target = configuration.getTarget();
    150         description.folder = new FolderConfiguration();
    151         description.folder.set(configuration.getFullConfig());
    152         description.locale = configuration.getLocale();
    153         description.device = configuration.getDevice();
    154         description.state = configuration.getDeviceState();
    155         description.activity = configuration.getActivity();
    156         return description;
    157     }
    158 
    159     /**
    160      * Initializes a string previously created with
    161      * {@link #toXml(Document)}
    162      *
    163      * @param project the project for this configuration's description
    164      * @param element the element to read back from
    165      * @param deviceList list of available devices
    166      * @return true if the configuration was initialized
    167      */
    168     @Nullable
    169     public static ConfigurationDescription fromXml(
    170             @Nullable IProject project,
    171             @NonNull Element element,
    172             @NonNull List<Device> deviceList) {
    173         ConfigurationDescription description = new ConfigurationDescription(project);
    174 
    175         if (!TAG_PREVIEW.equals(element.getTagName())) {
    176             return null;
    177         }
    178 
    179         String displayName = element.getAttribute(ATTR_NAME);
    180         if (!displayName.isEmpty()) {
    181             description.displayName = displayName;
    182         }
    183 
    184         String config = element.getAttribute(ATTR_CONFIG);
    185         Iterable<String> segments = Splitter.on('-').split(config);
    186         description.folder = FolderConfiguration.getConfig(segments);
    187 
    188         String theme = element.getAttribute(ATTR_THEME);
    189         if (!theme.isEmpty()) {
    190             description.theme = theme;
    191         }
    192 
    193         String targetId = element.getAttribute(ATTR_TARGET);
    194         if (!targetId.isEmpty()) {
    195             IAndroidTarget target = Configuration.stringToTarget(targetId);
    196             description.target = target;
    197         }
    198 
    199         String localeString = element.getAttribute(ATTR_LOCALE);
    200         if (!localeString.isEmpty()) {
    201             // Load locale. Note that this can get overwritten by the
    202             // project-wide settings read below.
    203             LanguageQualifier language = Locale.ANY_LANGUAGE;
    204             RegionQualifier region = Locale.ANY_REGION;
    205             String locales[] = localeString.split(SEP_LOCALE);
    206             if (locales[0].length() > 0) {
    207                 language = new LanguageQualifier(locales[0]);
    208             }
    209             if (locales.length > 1 && locales[1].length() > 0) {
    210                 region = new RegionQualifier(locales[1]);
    211             }
    212             description.locale = Locale.create(language, region);
    213         }
    214 
    215         String activity = element.getAttribute(ATTR_ACTIVITY);
    216         if (activity.isEmpty()) {
    217             activity = null;
    218         }
    219 
    220         String deviceString = element.getAttribute(ATTR_DEVICE);
    221         if (!deviceString.isEmpty()) {
    222             for (Device d : deviceList) {
    223                 if (d.getName().equals(deviceString)) {
    224                     description.device = d;
    225                     String stateName = element.getAttribute(ATTR_STATE);
    226                     if (stateName.isEmpty() || stateName.equals("null")) {
    227                         description.state = Configuration.getState(d, stateName);
    228                     } else if (d.getAllStates().size() > 0) {
    229                         description.state = d.getAllStates().get(0);
    230                     }
    231                     break;
    232                 }
    233             }
    234         }
    235 
    236         String uiModeString = element.getAttribute(ATTR_UIMODE);
    237         if (!uiModeString.isEmpty()) {
    238             description.uiMode = UiMode.getEnum(uiModeString);
    239             if (description.uiMode == null) {
    240                 description.uiMode = UiMode.NORMAL;
    241             }
    242         }
    243 
    244         String nightModeString = element.getAttribute(ATTR_NIGHTMODE);
    245         if (!nightModeString.isEmpty()) {
    246             description.nightMode = NightMode.getEnum(nightModeString);
    247             if (description.nightMode == null) {
    248                 description.nightMode = NightMode.NOTNIGHT;
    249             }
    250         }
    251 
    252 
    253         // Should I really be storing the FULL configuration? Might be trouble if
    254         // you bring a different device
    255 
    256         return description;
    257     }
    258 
    259     /**
    260      * Write this description into the given document as a new element.
    261      *
    262      * @param document the document to add the description to
    263      * @return the newly inserted element
    264      */
    265     @NonNull
    266     public Element toXml(Document document) {
    267         Element element = document.createElement(TAG_PREVIEW);
    268 
    269         element.setAttribute(ATTR_NAME, displayName);
    270         FolderConfiguration fullConfig = folder;
    271         String folderName = fullConfig.getFolderName(ResourceFolderType.LAYOUT);
    272         element.setAttribute(ATTR_CONFIG, folderName);
    273         if (theme != null) {
    274             element.setAttribute(ATTR_THEME, theme);
    275         }
    276         if (target != null) {
    277             element.setAttribute(ATTR_TARGET, Configuration.targetToString(target));
    278         }
    279 
    280         if (locale != null && (locale.hasLanguage() || locale.hasRegion())) {
    281             String value;
    282             if (locale.hasRegion()) {
    283                 value = locale.language.getValue() + SEP_LOCALE + locale.region.getValue();
    284             } else {
    285                 value = locale.language.getValue();
    286             }
    287             element.setAttribute(ATTR_LOCALE, value);
    288         }
    289 
    290         if (device != null) {
    291             element.setAttribute(ATTR_DEVICE, device.getName());
    292             if (state != null) {
    293                 element.setAttribute(ATTR_STATE, state.getName());
    294             }
    295         }
    296 
    297         if (activity != null) {
    298             element.setAttribute(ATTR_ACTIVITY, activity);
    299         }
    300 
    301         if (uiMode != null && uiMode != UiMode.NORMAL) {
    302             element.setAttribute(ATTR_UIMODE, uiMode.getResourceValue());
    303         }
    304 
    305         if (nightMode != null && nightMode != NightMode.NOTNIGHT) {
    306             element.setAttribute(ATTR_NIGHTMODE, nightMode.getResourceValue());
    307         }
    308 
    309         Element parent = document.getDocumentElement();
    310         if (parent == null) {
    311             parent = document.createElement(TAG_PREVIEWS);
    312             document.appendChild(parent);
    313         }
    314         parent.appendChild(element);
    315 
    316         return element;
    317     }
    318 
    319     /** Returns the preferred theme, or null */
    320     @Nullable
    321     String computePreferredTheme() {
    322         if (project == null) {
    323             return "Theme";
    324         }
    325         ManifestInfo manifest = ManifestInfo.get(project);
    326 
    327         // Look up the screen size for the current state
    328         ScreenSize screenSize = null;
    329         if (device != null) {
    330             List<State> states = device.getAllStates();
    331             for (State s : states) {
    332                 FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s);
    333                 if (folderConfig != null) {
    334                     ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
    335                     screenSize = qualifier.getValue();
    336                     break;
    337                 }
    338             }
    339         }
    340 
    341         // Look up the default/fallback theme to use for this project (which
    342         // depends on the screen size when no particular theme is specified
    343         // in the manifest)
    344         String defaultTheme = manifest.getDefaultTheme(target, screenSize);
    345 
    346         String preferred = defaultTheme;
    347         if (theme == null) {
    348             // If we are rendering a layout in included context, pick the theme
    349             // from the outer layout instead
    350 
    351             if (activity != null) {
    352                 Map<String, String> activityThemes = manifest.getActivityThemes();
    353                 preferred = activityThemes.get(activity);
    354             }
    355             if (preferred == null) {
    356                 preferred = defaultTheme;
    357             }
    358             theme = preferred;
    359         }
    360 
    361         return preferred;
    362     }
    363 
    364     private void checkThemePrefix() {
    365         if (theme != null && !theme.startsWith(PREFIX_RESOURCE_REF)) {
    366             if (theme.isEmpty()) {
    367                 computePreferredTheme();
    368                 return;
    369             }
    370 
    371             if (target != null) {
    372                 Sdk sdk = Sdk.getCurrent();
    373                 if (sdk != null) {
    374                     AndroidTargetData data = sdk.getTargetData(target);
    375 
    376                     if (data != null) {
    377                         ResourceRepository resources = data.getFrameworkResources();
    378                         if (resources != null
    379                             && resources.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) {
    380                             theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
    381                             return;
    382                         }
    383                     }
    384                 }
    385             }
    386 
    387             theme = STYLE_RESOURCE_PREFIX + theme;
    388         }
    389     }
    390 }
    391