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.PREFIX_RESOURCE_REF;
     21 import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
     22 
     23 import com.android.annotations.NonNull;
     24 import com.android.annotations.Nullable;
     25 import com.android.ide.common.rendering.LayoutLibrary;
     26 import com.android.ide.common.rendering.api.Capability;
     27 import com.android.ide.common.resources.ResourceFolder;
     28 import com.android.ide.common.resources.ResourceRepository;
     29 import com.android.ide.common.resources.configuration.DensityQualifier;
     30 import com.android.ide.common.resources.configuration.DeviceConfigHelper;
     31 import com.android.ide.common.resources.configuration.FolderConfiguration;
     32 import com.android.ide.common.resources.configuration.LanguageQualifier;
     33 import com.android.ide.common.resources.configuration.LayoutDirectionQualifier;
     34 import com.android.ide.common.resources.configuration.NightModeQualifier;
     35 import com.android.ide.common.resources.configuration.RegionQualifier;
     36 import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
     37 import com.android.ide.common.resources.configuration.UiModeQualifier;
     38 import com.android.ide.common.resources.configuration.VersionQualifier;
     39 import com.android.ide.eclipse.adt.AdtPlugin;
     40 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
     41 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     42 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes;
     43 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     44 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     45 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     46 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     47 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     48 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     49 import com.android.resources.Density;
     50 import com.android.resources.LayoutDirection;
     51 import com.android.resources.NightMode;
     52 import com.android.resources.ScreenSize;
     53 import com.android.resources.UiMode;
     54 import com.android.sdklib.AndroidVersion;
     55 import com.android.sdklib.IAndroidTarget;
     56 import com.android.sdklib.devices.Device;
     57 import com.android.sdklib.devices.State;
     58 import com.android.utils.Pair;
     59 import com.google.common.base.Objects;
     60 
     61 import org.eclipse.core.resources.IFile;
     62 import org.eclipse.core.resources.IProject;
     63 import org.eclipse.core.runtime.CoreException;
     64 import org.eclipse.core.runtime.QualifiedName;
     65 
     66 import java.util.List;
     67 
     68 /**
     69  * A {@linkplain Configuration} is a selection of device, orientation, theme,
     70  * etc for use when rendering a layout.
     71  */
     72 public class Configuration {
     73     /** The {@link FolderConfiguration} in change flags or override flags */
     74     public static final int CFG_FOLDER       = 1 << 0;
     75     /** The {@link Device} in change flags or override flags */
     76     public static final int CFG_DEVICE       = 1 << 1;
     77     /** The {@link State} in change flags or override flags */
     78     public static final int CFG_DEVICE_STATE = 1 << 2;
     79     /** The theme in change flags or override flags */
     80     public static final int CFG_THEME        = 1 << 3;
     81     /** The locale in change flags or override flags */
     82     public static final int CFG_LOCALE       = 1 << 4;
     83     /** The rendering {@link IAndroidTarget} in change flags or override flags */
     84     public static final int CFG_TARGET       = 1 << 5;
     85     /** The {@link NightMode} in change flags or override flags */
     86     public static final int CFG_NIGHT_MODE   = 1 << 6;
     87     /** The {@link UiMode} in change flags or override flags */
     88     public static final int CFG_UI_MODE      = 1 << 7;
     89     /** The {@link UiMode} in change flags or override flags */
     90     public static final int CFG_ACTIVITY     = 1 << 8;
     91 
     92     /** References all attributes */
     93     public static final int MASK_ALL = 0xFFFF;
     94 
     95     /** Attributes which affect which best-layout-file selection */
     96     public static final int MASK_FILE_ATTRS =
     97             CFG_DEVICE|CFG_DEVICE_STATE|CFG_LOCALE|CFG_TARGET|CFG_NIGHT_MODE|CFG_UI_MODE;
     98 
     99     /** Attributes which affect rendering appearance */
    100     public static final int MASK_RENDERING = MASK_FILE_ATTRS|CFG_THEME;
    101 
    102     /**
    103      * Setting name for project-wide setting controlling rendering target and locale which
    104      * is shared for all files
    105      */
    106     public final static QualifiedName NAME_RENDER_STATE =
    107         new QualifiedName(AdtPlugin.PLUGIN_ID, "render");          //$NON-NLS-1$
    108 
    109     private final static String MARKER_FRAMEWORK = "-";            //$NON-NLS-1$
    110     private final static String MARKER_PROJECT = "+";              //$NON-NLS-1$
    111     private final static String SEP = ":";                         //$NON-NLS-1$
    112     private final static String SEP_LOCALE = "-";                  //$NON-NLS-1$
    113 
    114     @NonNull
    115     protected ConfigurationChooser mConfigChooser;
    116 
    117     /** The {@link FolderConfiguration} representing the state of the UI controls */
    118     @NonNull
    119     protected final FolderConfiguration mFullConfig = new FolderConfiguration();
    120 
    121     /** The {@link FolderConfiguration} being edited. */
    122     @Nullable
    123     protected FolderConfiguration mEditedConfig;
    124 
    125     /** The target of the project of the file being edited. */
    126     @Nullable
    127     private IAndroidTarget mTarget;
    128 
    129     /** The theme style to render with */
    130     @Nullable
    131     private String mTheme;
    132 
    133     /** The device to render with */
    134     @Nullable
    135     private Device mDevice;
    136 
    137     /** The device state */
    138     @Nullable
    139     private State mState;
    140 
    141     /**
    142      * The activity associated with the layout. This is just a cached value of
    143      * the true value stored on the layout.
    144      */
    145     @Nullable
    146     private String mActivity;
    147 
    148     /** The locale to use for this configuration */
    149     @NonNull
    150     private Locale mLocale = Locale.ANY;
    151 
    152     /** UI mode */
    153     @NonNull
    154     private UiMode mUiMode = UiMode.NORMAL;
    155 
    156     /** Night mode */
    157     @NonNull
    158     private NightMode mNightMode = NightMode.NOTNIGHT;
    159 
    160     /** The display name */
    161     private String mDisplayName;
    162 
    163     /**
    164      * Creates a new {@linkplain Configuration}
    165      *
    166      * @param chooser the associated chooser
    167      */
    168     protected Configuration(@NonNull ConfigurationChooser chooser) {
    169         mConfigChooser = chooser;
    170     }
    171 
    172     /**
    173      * Sets the associated configuration chooser
    174      *
    175      * @param chooser the chooser
    176      */
    177     void setChooser(@NonNull ConfigurationChooser chooser) {
    178         // TODO: We should get rid of the binding between configurations
    179         // and configuration choosers. This is currently needed because
    180         // the choosers contain vital data such as the set of available
    181         // rendering targets, the set of available locales etc, which
    182         // also doesn't belong inside the configuration but is needed by it.
    183         mConfigChooser = chooser;
    184     }
    185 
    186     /**
    187      * Gets the associated configuration chooser
    188      *
    189      * @return the chooser
    190      */
    191     @NonNull
    192     ConfigurationChooser getChooser() {
    193         return mConfigChooser;
    194     }
    195 
    196     /**
    197      * Creates a new {@linkplain Configuration}
    198      *
    199      * @param chooser the associated chooser
    200      * @return a new configuration
    201      */
    202     @NonNull
    203     public static Configuration create(@NonNull ConfigurationChooser chooser) {
    204         return new Configuration(chooser);
    205     }
    206 
    207     /**
    208      * Creates a configuration suitable for the given file
    209      *
    210      * @param base the base configuration to base the file configuration off of
    211      * @param file the file to look up a configuration for
    212      * @return a suitable configuration
    213      */
    214     @NonNull
    215     public static Configuration create(
    216             @NonNull Configuration base,
    217             @NonNull IFile file) {
    218         Configuration configuration = copy(base);
    219         ConfigurationChooser chooser = base.getChooser();
    220         ProjectResources resources = chooser.getResources();
    221         ConfigurationMatcher matcher = new ConfigurationMatcher(chooser, configuration, file,
    222                 resources, false);
    223 
    224         ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
    225         configuration.mEditedConfig = new FolderConfiguration();
    226         configuration.mEditedConfig.set(resFolder.getConfiguration());
    227 
    228         matcher.adaptConfigSelection(true /*needBestMatch*/);
    229         configuration.syncFolderConfig();
    230 
    231         return configuration;
    232     }
    233 
    234     /**
    235      * Creates a new {@linkplain Configuration} that is a copy from a different configuration
    236      *
    237      * @param original the original to copy from
    238      * @return a new configuration copied from the original
    239      */
    240     @NonNull
    241     public static Configuration copy(@NonNull Configuration original) {
    242         Configuration copy = create(original.mConfigChooser);
    243         copy.mFullConfig.set(original.mFullConfig);
    244         if (original.mEditedConfig != null) {
    245             copy.mEditedConfig = new FolderConfiguration();
    246             copy.mEditedConfig.set(original.mEditedConfig);
    247         }
    248         copy.mTarget = original.getTarget();
    249         copy.mTheme = original.getTheme();
    250         copy.mDevice = original.getDevice();
    251         copy.mState = original.getDeviceState();
    252         copy.mActivity = original.getActivity();
    253         copy.mLocale = original.getLocale();
    254         copy.mUiMode = original.getUiMode();
    255         copy.mNightMode = original.getNightMode();
    256         copy.mDisplayName = original.getDisplayName();
    257 
    258         return copy;
    259     }
    260 
    261     /**
    262      * Returns the associated activity
    263      *
    264      * @return the activity
    265      */
    266     @Nullable
    267     public String getActivity() {
    268         return mActivity;
    269     }
    270 
    271     /**
    272      * Returns the chosen device.
    273      *
    274      * @return the chosen device
    275      */
    276     @Nullable
    277     public Device getDevice() {
    278         return mDevice;
    279     }
    280 
    281     /**
    282      * Returns the chosen device state
    283      *
    284      * @return the device state
    285      */
    286     @Nullable
    287     public State getDeviceState() {
    288         return mState;
    289     }
    290 
    291     /**
    292      * Returns the chosen locale
    293      *
    294      * @return the locale
    295      */
    296     @NonNull
    297     public Locale getLocale() {
    298         return mLocale;
    299     }
    300 
    301     /**
    302      * Returns the UI mode
    303      *
    304      * @return the UI mode
    305      */
    306     @NonNull
    307     public UiMode getUiMode() {
    308         return mUiMode;
    309     }
    310 
    311     /**
    312      * Returns the day/night mode
    313      *
    314      * @return the night mode
    315      */
    316     @NonNull
    317     public NightMode getNightMode() {
    318         return mNightMode;
    319     }
    320 
    321     /**
    322      * Returns the current theme style
    323      *
    324      * @return the theme style
    325      */
    326     @Nullable
    327     public String getTheme() {
    328         return mTheme;
    329     }
    330 
    331     /**
    332      * Returns the rendering target
    333      *
    334      * @return the target
    335      */
    336     @Nullable
    337     public IAndroidTarget getTarget() {
    338         return mTarget;
    339     }
    340 
    341     /**
    342      * Returns the display name to show for this configuration
    343      *
    344      * @return the display name, or null if none has been assigned
    345      */
    346     @Nullable
    347     public String getDisplayName() {
    348         return mDisplayName;
    349     }
    350 
    351     /**
    352      * Returns whether the configuration's theme is a project theme.
    353      * <p/>
    354      * The returned value is meaningless if {@link #getTheme()} returns
    355      * <code>null</code>.
    356      *
    357      * @return true for project a theme, false for a framework theme
    358      */
    359     public boolean isProjectTheme() {
    360         String theme = getTheme();
    361         if (theme != null) {
    362             assert theme.startsWith(STYLE_RESOURCE_PREFIX)
    363                 || theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
    364 
    365             return ResourceHelper.isProjectStyle(theme);
    366         }
    367 
    368         return false;
    369     }
    370 
    371     /**
    372      * Returns true if the current layout is locale-specific
    373      *
    374      * @return if this configuration represents a locale-specific layout
    375      */
    376     public boolean isLocaleSpecificLayout() {
    377         return mEditedConfig == null || mEditedConfig.getLanguageQualifier() != null;
    378     }
    379 
    380     /**
    381      * Returns the full, complete {@link FolderConfiguration}
    382      *
    383      * @return the full configuration
    384      */
    385     @NonNull
    386     public FolderConfiguration getFullConfig() {
    387         return mFullConfig;
    388     }
    389 
    390     /**
    391      * Copies the full, complete {@link FolderConfiguration} into the given
    392      * folder config instance.
    393      *
    394      * @param dest the {@link FolderConfiguration} instance to copy into
    395      */
    396     public void copyFullConfig(FolderConfiguration dest) {
    397         dest.set(mFullConfig);
    398     }
    399 
    400     /**
    401      * Returns the edited {@link FolderConfiguration} (this is not a full
    402      * configuration, so you can think of it as the "constraints" used by the
    403      * {@link ConfigurationMatcher} to produce a full configuration.
    404      *
    405      * @return the constraints configuration
    406      */
    407     @NonNull
    408     public FolderConfiguration getEditedConfig() {
    409         return mEditedConfig;
    410     }
    411 
    412     /**
    413      * Sets the edited {@link FolderConfiguration} (this is not a full
    414      * configuration, so you can think of it as the "constraints" used by the
    415      * {@link ConfigurationMatcher} to produce a full configuration.
    416      *
    417      * @param editedConfig the constraints configuration
    418      */
    419     public void setEditedConfig(@NonNull FolderConfiguration editedConfig) {
    420         mEditedConfig = editedConfig;
    421     }
    422 
    423     /**
    424      * Sets the associated activity
    425      *
    426      * @param activity the activity
    427      */
    428     public void setActivity(String activity) {
    429         mActivity = activity;
    430     }
    431 
    432     /**
    433      * Sets the device
    434      *
    435      * @param device the device
    436      * @param skipSync if true, don't sync folder configuration (typically because
    437      *   you are going to set other configuration parameters and you'll call
    438      *   {@link #syncFolderConfig()} once at the end)
    439      */
    440     public void setDevice(Device device, boolean skipSync) {
    441         mDevice = device;
    442 
    443         if (!skipSync) {
    444             syncFolderConfig();
    445         }
    446     }
    447 
    448     /**
    449      * Sets the device state
    450      *
    451      * @param state the device state
    452      * @param skipSync if true, don't sync folder configuration (typically because
    453      *   you are going to set other configuration parameters and you'll call
    454      *   {@link #syncFolderConfig()} once at the end)
    455      */
    456     public void setDeviceState(State state, boolean skipSync) {
    457         mState = state;
    458 
    459         if (!skipSync) {
    460             syncFolderConfig();
    461         }
    462     }
    463 
    464     /**
    465      * Sets the locale
    466      *
    467      * @param locale the locale
    468      * @param skipSync if true, don't sync folder configuration (typically because
    469      *   you are going to set other configuration parameters and you'll call
    470      *   {@link #syncFolderConfig()} once at the end)
    471      */
    472     public void setLocale(@NonNull Locale locale, boolean skipSync) {
    473         mLocale = locale;
    474 
    475         if (!skipSync) {
    476             syncFolderConfig();
    477         }
    478     }
    479 
    480     /**
    481      * Sets the rendering target
    482      *
    483      * @param target rendering target
    484      * @param skipSync if true, don't sync folder configuration (typically because
    485      *   you are going to set other configuration parameters and you'll call
    486      *   {@link #syncFolderConfig()} once at the end)
    487      */
    488     public void setTarget(IAndroidTarget target, boolean skipSync) {
    489         mTarget = target;
    490 
    491         if (!skipSync) {
    492             syncFolderConfig();
    493         }
    494     }
    495 
    496     /**
    497      * Sets the display name to be shown for this configuration.
    498      *
    499      * @param displayName the new display name
    500      */
    501     public void setDisplayName(@Nullable String displayName) {
    502         mDisplayName = displayName;
    503     }
    504 
    505     /**
    506      * Sets the night mode
    507      *
    508      * @param night the night mode
    509      * @param skipSync if true, don't sync folder configuration (typically because
    510      *   you are going to set other configuration parameters and you'll call
    511      *   {@link #syncFolderConfig()} once at the end)
    512      */
    513     public void setNightMode(@NonNull NightMode night, boolean skipSync) {
    514         mNightMode = night;
    515 
    516         if (!skipSync) {
    517             syncFolderConfig();
    518         }
    519     }
    520 
    521     /**
    522      * Sets the UI mode
    523      *
    524      * @param uiMode the UI mode
    525      * @param skipSync if true, don't sync folder configuration (typically because
    526      *   you are going to set other configuration parameters and you'll call
    527      *   {@link #syncFolderConfig()} once at the end)
    528      */
    529     public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) {
    530         mUiMode = uiMode;
    531 
    532         if (!skipSync) {
    533             syncFolderConfig();
    534         }
    535     }
    536 
    537     /**
    538      * Sets the theme style
    539      *
    540      * @param theme the theme
    541      */
    542     public void setTheme(String theme) {
    543         mTheme = theme;
    544         checkThemePrefix();
    545     }
    546 
    547     /**
    548      * Updates the folder configuration such that it reflects changes in
    549      * configuration state such as the device orientation, the UI mode, the
    550      * rendering target, etc.
    551      */
    552     public void syncFolderConfig() {
    553         Device device = getDevice();
    554         if (device == null) {
    555             return;
    556         }
    557 
    558         // get the device config from the device/state combos.
    559         FolderConfiguration config = DeviceConfigHelper.getFolderConfig(getDeviceState());
    560 
    561         // replace the config with the one from the device
    562         mFullConfig.set(config);
    563 
    564         // sync the selected locale
    565         Locale locale = getLocale();
    566         mFullConfig.setLanguageQualifier(locale.language);
    567         mFullConfig.setRegionQualifier(locale.region);
    568         if (!locale.hasLanguage()) {
    569             // Avoid getting the layout library if the locale doesn't have any language.
    570             mFullConfig.setLayoutDirectionQualifier(
    571                     new LayoutDirectionQualifier(LayoutDirection.LTR));
    572         } else {
    573             Sdk currentSdk = Sdk.getCurrent();
    574             if (currentSdk != null) {
    575                 AndroidTargetData targetData = currentSdk.getTargetData(getTarget());
    576                 if (targetData != null) {
    577                     LayoutLibrary layoutLib = targetData.getLayoutLibrary();
    578                     if (layoutLib != null) {
    579                         if (layoutLib.isRtl(locale.toLocaleId())) {
    580                             mFullConfig.setLayoutDirectionQualifier(
    581                                     new LayoutDirectionQualifier(LayoutDirection.RTL));
    582                         } else {
    583                             mFullConfig.setLayoutDirectionQualifier(
    584                                     new LayoutDirectionQualifier(LayoutDirection.LTR));
    585                         }
    586                     }
    587                 }
    588             }
    589         }
    590 
    591         // Replace the UiMode with the selected one, if one is selected
    592         UiMode uiMode = getUiMode();
    593         if (uiMode != null) {
    594             mFullConfig.setUiModeQualifier(new UiModeQualifier(uiMode));
    595         }
    596 
    597         // Replace the NightMode with the selected one, if one is selected
    598         NightMode nightMode = getNightMode();
    599         if (nightMode != null) {
    600             mFullConfig.setNightModeQualifier(new NightModeQualifier(nightMode));
    601         }
    602 
    603         // replace the API level by the selection of the combo
    604         IAndroidTarget target = getTarget();
    605         if (target == null && mConfigChooser != null) {
    606             target = mConfigChooser.getProjectTarget();
    607         }
    608         if (target != null) {
    609             int apiLevel = target.getVersion().getApiLevel();
    610             mFullConfig.setVersionQualifier(new VersionQualifier(apiLevel));
    611         }
    612     }
    613 
    614     /**
    615      * Creates a string suitable for persistence, which can be initialized back
    616      * to a configuration via {@link #initialize(String)}
    617      *
    618      * @return a persistent string
    619      */
    620     @NonNull
    621     public String toPersistentString() {
    622         StringBuilder sb = new StringBuilder(32);
    623         Device device = getDevice();
    624         if (device != null) {
    625             sb.append(device.getName());
    626             sb.append(SEP);
    627             State state = getDeviceState();
    628             if (state != null) {
    629                 sb.append(state.getName());
    630             }
    631             sb.append(SEP);
    632             Locale locale = getLocale();
    633             if (isLocaleSpecificLayout() && locale != null) {
    634                 // locale[0]/[1] can be null sometimes when starting Eclipse
    635                 sb.append(locale.language.getValue());
    636                 sb.append(SEP_LOCALE);
    637                 sb.append(locale.region.getValue());
    638             }
    639             sb.append(SEP);
    640             // Need to escape the theme: if we write the full theme style, then
    641             // we can end up with ":"'s in the string (as in @android:style/Theme) which
    642             // can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}.
    643             String theme = getTheme();
    644             if (theme != null) {
    645                 String themeName = ResourceHelper.styleToTheme(theme);
    646                 if (theme.startsWith(STYLE_RESOURCE_PREFIX)) {
    647                     sb.append(MARKER_PROJECT);
    648                 } else if (theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
    649                     sb.append(MARKER_FRAMEWORK);
    650                 }
    651                 sb.append(themeName);
    652             }
    653             sb.append(SEP);
    654             UiMode uiMode = getUiMode();
    655             if (uiMode != null) {
    656                 sb.append(uiMode.getResourceValue());
    657             }
    658             sb.append(SEP);
    659             NightMode nightMode = getNightMode();
    660             if (nightMode != null) {
    661                 sb.append(nightMode.getResourceValue());
    662             }
    663             sb.append(SEP);
    664 
    665             // We used to store the render target here in R9. Leave a marker
    666             // to ensure that we don't reuse this slot; add new extra fields after it.
    667             sb.append(SEP);
    668             String activity = getActivity();
    669             if (activity != null) {
    670                 sb.append(activity);
    671             }
    672         }
    673 
    674         return sb.toString();
    675     }
    676 
    677     /** Returns the preferred theme, or null */
    678     @Nullable
    679     String computePreferredTheme() {
    680         IProject project = mConfigChooser.getProject();
    681         ManifestInfo manifest = ManifestInfo.get(project);
    682 
    683         // Look up the screen size for the current state
    684         ScreenSize screenSize = null;
    685         Device device = getDevice();
    686         if (device != null) {
    687             List<State> states = device.getAllStates();
    688             for (State state : states) {
    689                 FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
    690                 if (folderConfig != null) {
    691                     ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
    692                     screenSize = qualifier.getValue();
    693                     break;
    694                 }
    695             }
    696         }
    697 
    698         // Look up the default/fallback theme to use for this project (which
    699         // depends on the screen size when no particular theme is specified
    700         // in the manifest)
    701         String defaultTheme = manifest.getDefaultTheme(getTarget(), screenSize);
    702 
    703         String preferred = defaultTheme;
    704         if (getTheme() == null) {
    705             // If we are rendering a layout in included context, pick the theme
    706             // from the outer layout instead
    707 
    708             String activity = getActivity();
    709             if (activity != null) {
    710                 ActivityAttributes attributes = manifest.getActivityAttributes(activity);
    711                 if (attributes != null) {
    712                     preferred = attributes.getTheme();
    713                 }
    714             }
    715             if (preferred == null) {
    716                 preferred = defaultTheme;
    717             }
    718             setTheme(preferred);
    719         }
    720 
    721         return preferred;
    722     }
    723 
    724     private void checkThemePrefix() {
    725         if (mTheme != null && !mTheme.startsWith(PREFIX_RESOURCE_REF)) {
    726             if (mTheme.isEmpty()) {
    727                 computePreferredTheme();
    728                 return;
    729             }
    730             ResourceRepository frameworkRes = mConfigChooser.getClient().getFrameworkResources();
    731             if (frameworkRes != null
    732                     && frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + mTheme)) {
    733                 mTheme = ANDROID_STYLE_RESOURCE_PREFIX + mTheme;
    734             } else {
    735                 mTheme = STYLE_RESOURCE_PREFIX + mTheme;
    736             }
    737         }
    738     }
    739 
    740     /**
    741      * Initializes a string previously created with
    742      * {@link #toPersistentString()}
    743      *
    744      * @param data the string to initialize back from
    745      * @return true if the configuration was initialized
    746      */
    747     boolean initialize(String data) {
    748         String[] values = data.split(SEP);
    749         if (values.length >= 6 && values.length <= 8) {
    750             for (Device d : mConfigChooser.getDeviceList()) {
    751                 if (d.getName().equals(values[0])) {
    752                     mDevice = d;
    753                     String stateName = null;
    754                     FolderConfiguration config = null;
    755                     if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$
    756                         stateName = values[1];
    757                         config = DeviceConfigHelper.getFolderConfig(mDevice, stateName);
    758                     } else if (mDevice.getAllStates().size() > 0) {
    759                         State first = mDevice.getAllStates().get(0);
    760                         stateName = first.getName();
    761                         config = DeviceConfigHelper.getFolderConfig(first);
    762                     }
    763                     mState = getState(mDevice, stateName);
    764                     if (config != null) {
    765                         // Load locale. Note that this can get overwritten by the
    766                         // project-wide settings read below.
    767                         LanguageQualifier language = Locale.ANY_LANGUAGE;
    768                         RegionQualifier region = Locale.ANY_REGION;
    769                         String locales[] = values[2].split(SEP_LOCALE);
    770                         if (locales.length >= 2) {
    771                             if (locales[0].length() > 0) {
    772                                 language = new LanguageQualifier(locales[0]);
    773                             }
    774                             if (locales[1].length() > 0) {
    775                                 region = new RegionQualifier(locales[1]);
    776                             }
    777                             mLocale = Locale.create(language, region);
    778                         }
    779 
    780                         // Decode the theme name: See {@link #getData}
    781                         mTheme = values[3];
    782                         if (mTheme.startsWith(MARKER_FRAMEWORK)) {
    783                             mTheme = ANDROID_STYLE_RESOURCE_PREFIX
    784                                     + mTheme.substring(MARKER_FRAMEWORK.length());
    785                         } else if (mTheme.startsWith(MARKER_PROJECT)) {
    786                             mTheme = STYLE_RESOURCE_PREFIX
    787                                     + mTheme.substring(MARKER_PROJECT.length());
    788                         } else {
    789                             checkThemePrefix();
    790                         }
    791 
    792                         mUiMode = UiMode.getEnum(values[4]);
    793                         if (mUiMode == null) {
    794                             mUiMode = UiMode.NORMAL;
    795                         }
    796                         mNightMode = NightMode.getEnum(values[5]);
    797                         if (mNightMode == null) {
    798                             mNightMode = NightMode.NOTNIGHT;
    799                         }
    800 
    801                         // element 7/values[6]: used to store render target in R9.
    802                         // No longer stored here. If adding more data, make
    803                         // sure you leave 7 alone.
    804 
    805                         Pair<Locale, IAndroidTarget> pair = loadRenderState(mConfigChooser);
    806                         if (pair != null) {
    807                             // We only use the "global" setting
    808                             if (!isLocaleSpecificLayout()) {
    809                                 mLocale = pair.getFirst();
    810                             }
    811                             mTarget = pair.getSecond();
    812                         }
    813 
    814                         if (values.length == 8) {
    815                             mActivity = values[7];
    816                         }
    817 
    818                         return true;
    819                     }
    820                 }
    821             }
    822         }
    823 
    824         return false;
    825     }
    826 
    827     /**
    828      * Loads the render state (the locale and the render target, which are shared among
    829      * all the layouts meaning that changing it in one will change it in all) and returns
    830      * the current project-wide locale and render target to be used.
    831      *
    832      * @param chooser the {@link ConfigurationChooser} providing information about
    833      *     loaded targets
    834      * @return a pair of a locale and a render target
    835      */
    836     @Nullable
    837     static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) {
    838         IProject project = chooser.getProject();
    839         if (project == null || !project.isAccessible()) {
    840             return null;
    841         }
    842 
    843         try {
    844             String data = project.getPersistentProperty(NAME_RENDER_STATE);
    845             if (data != null) {
    846                 Locale locale = Locale.ANY;
    847                 IAndroidTarget target = null;
    848 
    849                 String[] values = data.split(SEP);
    850                 if (values.length == 2) {
    851                     LanguageQualifier language = Locale.ANY_LANGUAGE;
    852                     RegionQualifier region = Locale.ANY_REGION;
    853                     String locales[] = values[0].split(SEP_LOCALE);
    854                     if (locales.length >= 2) {
    855                         if (locales[0].length() > 0) {
    856                             language = new LanguageQualifier(locales[0]);
    857                         }
    858                         if (locales[1].length() > 0) {
    859                             region = new RegionQualifier(locales[1]);
    860                         }
    861                     }
    862                     locale = Locale.create(language, region);
    863 
    864                     if (AdtPrefs.getPrefs().isAutoPickRenderTarget()) {
    865                         target = ConfigurationMatcher.findDefaultRenderTarget(chooser);
    866                     } else {
    867                         String targetString = values[1];
    868                         target = stringToTarget(chooser, targetString);
    869                         // See if we should "correct" the rendering target to a
    870                         // better version. If you're using a pre-release version
    871                         // of the render target, and a final release is
    872                         // available and installed, we should switch to that
    873                         // one instead.
    874                         if (target != null) {
    875                             AndroidVersion version = target.getVersion();
    876                             List<IAndroidTarget> targetList = chooser.getTargetList();
    877                             if (version.getCodename() != null && targetList != null) {
    878                                 int targetApiLevel = version.getApiLevel() + 1;
    879                                 for (IAndroidTarget t : targetList) {
    880                                     if (t.getVersion().getApiLevel() == targetApiLevel
    881                                             && t.isPlatform()) {
    882                                         target = t;
    883                                         break;
    884                                     }
    885                                 }
    886                             }
    887                         } else {
    888                             target = ConfigurationMatcher.findDefaultRenderTarget(chooser);
    889                         }
    890                     }
    891                 }
    892 
    893                 return Pair.of(locale, target);
    894             }
    895 
    896             return Pair.of(Locale.ANY, ConfigurationMatcher.findDefaultRenderTarget(chooser));
    897         } catch (CoreException e) {
    898             AdtPlugin.log(e, null);
    899         }
    900 
    901         return null;
    902     }
    903 
    904     /**
    905      * Saves the render state (the current locale and render target settings) into the
    906      * project wide settings storage
    907      */
    908     void saveRenderState() {
    909         IProject project = mConfigChooser.getProject();
    910         if (project == null) {
    911             return;
    912         }
    913         try {
    914             // Generate a persistent string from locale+target
    915             StringBuilder sb = new StringBuilder(32);
    916             Locale locale = getLocale();
    917             if (locale != null) {
    918                 // locale[0]/[1] can be null sometimes when starting Eclipse
    919                 sb.append(locale.language.getValue());
    920                 sb.append(SEP_LOCALE);
    921                 sb.append(locale.region.getValue());
    922             }
    923             sb.append(SEP);
    924             IAndroidTarget target = getTarget();
    925             if (target != null) {
    926                 sb.append(targetToString(target));
    927                 sb.append(SEP);
    928             }
    929 
    930             project.setPersistentProperty(NAME_RENDER_STATE, sb.toString());
    931         } catch (CoreException e) {
    932             AdtPlugin.log(e, null);
    933         }
    934     }
    935 
    936     /**
    937      * Returns a String id to represent an {@link IAndroidTarget} which can be translated
    938      * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id
    939      * will never contain the {@link #SEP} character.
    940      *
    941      * @param target the target to return an id for
    942      * @return an id for the given target; never null
    943      */
    944     @NonNull
    945     public static String targetToString(@NonNull IAndroidTarget target) {
    946         return target.getFullName().replace(SEP, "");  //$NON-NLS-1$
    947     }
    948 
    949     /**
    950      * Returns an {@link IAndroidTarget} that corresponds to the given id that was
    951      * originally returned by {@link #targetToString}. May be null, if the platform is no
    952      * longer available, or if the platform list has not yet been initialized.
    953      *
    954      * @param chooser the {@link ConfigurationChooser} providing information about
    955      *     loaded targets
    956      * @param id the id that corresponds to the desired platform
    957      * @return an {@link IAndroidTarget} that matches the given id, or null
    958      */
    959     @Nullable
    960     public static IAndroidTarget stringToTarget(
    961             @NonNull ConfigurationChooser chooser,
    962             @NonNull String id) {
    963         List<IAndroidTarget> targetList = chooser.getTargetList();
    964         if (targetList != null && targetList.size() > 0) {
    965             for (IAndroidTarget target : targetList) {
    966                 if (id.equals(targetToString(target))) {
    967                     return target;
    968                 }
    969             }
    970         }
    971 
    972         return null;
    973     }
    974 
    975     /**
    976      * Returns an {@link IAndroidTarget} that corresponds to the given id that was
    977      * originally returned by {@link #targetToString}. May be null, if the platform is no
    978      * longer available, or if the platform list has not yet been initialized.
    979      *
    980      * @param id the id that corresponds to the desired platform
    981      * @return an {@link IAndroidTarget} that matches the given id, or null
    982      */
    983     @Nullable
    984     public static IAndroidTarget stringToTarget(
    985             @NonNull String id) {
    986         Sdk currentSdk = Sdk.getCurrent();
    987         if (currentSdk != null) {
    988             IAndroidTarget[] targets = currentSdk.getTargets();
    989             for (IAndroidTarget target : targets) {
    990                 if (id.equals(targetToString(target))) {
    991                     return target;
    992                 }
    993             }
    994         }
    995 
    996         return null;
    997     }
    998 
    999     /**
   1000      * Returns the {@link State} by the given name for the given {@link Device}
   1001      *
   1002      * @param device the device
   1003      * @param name the name of the state
   1004      */
   1005     @Nullable
   1006     static State getState(@Nullable Device device, @Nullable String name) {
   1007         if (device == null) {
   1008             return null;
   1009         } else if (name != null) {
   1010             State state = device.getState(name);
   1011             if (state != null) {
   1012                 return state;
   1013             }
   1014         }
   1015 
   1016         return device.getDefaultState();
   1017     }
   1018 
   1019     /**
   1020      * Returns the currently selected {@link Density}. This is guaranteed to be non null.
   1021      *
   1022      * @return the density
   1023      */
   1024     @NonNull
   1025     public Density getDensity() {
   1026         if (mFullConfig != null) {
   1027             DensityQualifier qual = mFullConfig.getDensityQualifier();
   1028             if (qual != null) {
   1029                 // just a sanity check
   1030                 Density d = qual.getValue();
   1031                 if (d != Density.NODPI) {
   1032                     return d;
   1033                 }
   1034             }
   1035         }
   1036 
   1037         // no config? return medium as the default density.
   1038         return Density.MEDIUM;
   1039     }
   1040 
   1041     /**
   1042      * Get the next cyclical state after the given state
   1043      *
   1044      * @param from the state to start with
   1045      * @return the following state following
   1046      */
   1047     @Nullable
   1048     public State getNextDeviceState(@Nullable State from) {
   1049         Device device = getDevice();
   1050         if (device == null) {
   1051             return null;
   1052         }
   1053         List<State> states = device.getAllStates();
   1054         for (int i = 0; i < states.size(); i++) {
   1055             if (states.get(i) == from) {
   1056                 return states.get((i + 1) % states.size());
   1057             }
   1058         }
   1059 
   1060         return null;
   1061     }
   1062 
   1063     /**
   1064      * Returns true if this configuration supports the given rendering
   1065      * capability
   1066      *
   1067      * @param capability the capability to check
   1068      * @return true if the capability is supported
   1069      */
   1070     public boolean supports(Capability capability) {
   1071         IAndroidTarget target = getTarget();
   1072         if (target != null) {
   1073             return RenderService.supports(target, capability);
   1074         }
   1075 
   1076         return false;
   1077     }
   1078 
   1079     @Override
   1080     public String toString() {
   1081         return Objects.toStringHelper(this.getClass())
   1082                 .add("display", getDisplayName())                 //$NON-NLS-1$
   1083                 .add("persistent", toPersistentString())          //$NON-NLS-1$
   1084                 .toString();
   1085     }
   1086 }
   1087