• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
Configuration(@onNull ConfigurationChooser chooser)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      */
setChooser(@onNull ConfigurationChooser chooser)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
getChooser()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
create(@onNull ConfigurationChooser chooser)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
create( @onNull Configuration base, @NonNull IFile file)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
copy(@onNull Configuration original)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
getActivity()263     public String getActivity() {
264         return mActivity;
265     }
266 
267     /**
268      * Returns the chosen device.
269      *
270      * @return the chosen device
271      */
272     @Nullable
getDevice()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
getDeviceState()283     public State getDeviceState() {
284         return mState;
285     }
286 
287     /**
288      * Returns the chosen locale
289      *
290      * @return the locale
291      */
292     @NonNull
getLocale()293     public Locale getLocale() {
294         return mLocale;
295     }
296 
297     /**
298      * Returns the UI mode
299      *
300      * @return the UI mode
301      */
302     @NonNull
getUiMode()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
getNightMode()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
getTheme()323     public String getTheme() {
324         return mTheme;
325     }
326 
327     /**
328      * Returns the rendering target
329      *
330      * @return the target
331      */
332     @Nullable
getTarget()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
getDisplayName()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      */
isProjectTheme()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      */
isLocaleSpecificLayout()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
getFullConfig()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      */
copyFullConfig(FolderConfiguration dest)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
getEditedConfig()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      */
setEditedConfig(@onNull FolderConfiguration editedConfig)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      */
setActivity(String activity)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      */
setDevice(Device device, boolean skipSync)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      */
setDeviceState(State state, boolean skipSync)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      */
setLocale(@onNull Locale locale, boolean skipSync)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      */
setTarget(IAndroidTarget target, boolean skipSync)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      */
setDisplayName(@ullable String displayName)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      */
setNightMode(@onNull NightMode night, boolean skipSync)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      */
setUiMode(@onNull UiMode uiMode, boolean skipSync)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      */
setTheme(String theme)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      */
syncFolderConfig()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
toPersistentString()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
computePreferredTheme()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 
checkThemePrefix()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      */
initialize(String data)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
loadRenderState(ConfigurationChooser chooser)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      */
saveRenderState()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
targetToString(@onNull IAndroidTarget target)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
stringToTarget( @onNull ConfigurationChooser chooser, @NonNull String id)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
stringToTarget( @onNull String id)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
getState(@ullable Device device, @Nullable String name)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
getDensity()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
getNextDeviceState(@ullable State from)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      */
supports(Capability capability)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
toString()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