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