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