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