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