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 package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
17 
18 import com.android.annotations.NonNull;
19 import com.android.annotations.Nullable;
20 import com.android.ide.common.resources.configuration.FolderConfiguration;
21 import com.android.resources.NightMode;
22 import com.android.resources.UiMode;
23 import com.android.sdklib.IAndroidTarget;
24 import com.android.sdklib.devices.Device;
25 import com.android.sdklib.devices.State;
26 import com.google.common.base.Objects;
27 
28 /**
29  * An {@linkplain NestedConfiguration} is a {@link Configuration} which inherits
30  * all of its values from a different configuration, except for one or more
31  * attributes where it overrides a custom value.
32  * <p>
33  * Unlike a {@link VaryingConfiguration}, a {@linkplain NestedConfiguration}
34  * will always return the same overridden value, regardless of the inherited
35  * value.
36  * <p>
37  * For example, an {@linkplain NestedConfiguration} may fix the locale to always
38  * be "en", but otherwise inherit everything else.
39  */
40 public class NestedConfiguration extends Configuration {
41     /** The configuration we are inheriting non-overridden values from */
42     protected Configuration mParent;
43 
44     /** Bitmask of attributes to be overridden in this configuration */
45     private int mOverride;
46 
47     /**
48      * Constructs a new {@linkplain NestedConfiguration}.
49      * Construct via
50      *
51      * @param chooser the associated chooser
52      * @param configuration the configuration to inherit from
53      */
NestedConfiguration( @onNull ConfigurationChooser chooser, @NonNull Configuration configuration)54     protected NestedConfiguration(
55             @NonNull ConfigurationChooser chooser,
56             @NonNull Configuration configuration) {
57         super(chooser);
58         mParent = configuration;
59 
60         mFullConfig.set(mParent.mFullConfig);
61         if (mParent.getEditedConfig() != null) {
62             mEditedConfig = new FolderConfiguration();
63             mEditedConfig.set(mParent.mEditedConfig);
64         }
65     }
66 
67     /**
68      * Returns the override flags for this configuration. Corresponds to
69      * the {@code CFG_} flags in {@link ConfigurationClient}.
70      *
71      * @return the bitmask
72      */
getOverrideFlags()73     public int getOverrideFlags() {
74         return mOverride;
75     }
76 
77     /**
78      * Creates a new {@linkplain NestedConfiguration} that has the same overriding
79      * attributes as the given other {@linkplain NestedConfiguration}, and gets
80      * its values from the given {@linkplain Configuration}.
81      *
82      * @param other the configuration to copy overrides from
83      * @param values the configuration to copy values from
84      * @param parent the parent to tie the configuration to for inheriting values
85      * @return a new configuration
86      */
87     @NonNull
create( @onNull NestedConfiguration other, @NonNull Configuration values, @NonNull Configuration parent)88     public static NestedConfiguration create(
89             @NonNull NestedConfiguration other,
90             @NonNull Configuration values,
91             @NonNull Configuration parent) {
92         NestedConfiguration configuration =
93                 new NestedConfiguration(other.mConfigChooser, parent);
94         initFrom(configuration, other, values, true /*sync*/);
95         return configuration;
96     }
97 
98     /**
99      * Initializes a new {@linkplain NestedConfiguration} with the overriding
100      * attributes as the given other {@linkplain NestedConfiguration}, and gets
101      * its values from the given {@linkplain Configuration}.
102      *
103      * @param configuration the configuration to initialize
104      * @param other the configuration to copy overrides from
105      * @param values the configuration to copy values from
106      * @param sync if true, sync the folder configuration from
107      */
initFrom(NestedConfiguration configuration, NestedConfiguration other, Configuration values, boolean sync)108     protected static void initFrom(NestedConfiguration configuration,
109             NestedConfiguration other, Configuration values, boolean sync) {
110         configuration.mOverride = other.mOverride;
111         configuration.setDisplayName(values.getDisplayName());
112         configuration.setActivity(values.getActivity());
113 
114         if (configuration.isOverridingLocale()) {
115             configuration.setLocale(values.getLocale(), true);
116         }
117         if (configuration.isOverridingTarget()) {
118             configuration.setTarget(values.getTarget(), true);
119         }
120         if (configuration.isOverridingDevice()) {
121             configuration.setDevice(values.getDevice(), true);
122         }
123         if (configuration.isOverridingDeviceState()) {
124             configuration.setDeviceState(values.getDeviceState(), true);
125         }
126         if (configuration.isOverridingNightMode()) {
127             configuration.setNightMode(values.getNightMode(), true);
128         }
129         if (configuration.isOverridingUiMode()) {
130             configuration.setUiMode(values.getUiMode(), true);
131         }
132         if (sync) {
133             configuration.syncFolderConfig();
134         }
135     }
136 
137     /**
138      * Sets the parent configuration that this configuration is inheriting from.
139      *
140      * @param parent the parent configuration
141      */
setParent(@onNull Configuration parent)142     public void setParent(@NonNull Configuration parent) {
143         mParent = parent;
144     }
145 
146     /**
147      * Creates a new {@linkplain Configuration} which inherits values from the
148      * given parent {@linkplain Configuration}, possibly overriding some as
149      * well.
150      *
151      * @param chooser the associated chooser
152      * @param parent the configuration to inherit values from
153      * @return a new configuration
154      */
155     @NonNull
create(@onNull ConfigurationChooser chooser, @NonNull Configuration parent)156     public static NestedConfiguration create(@NonNull ConfigurationChooser chooser,
157             @NonNull Configuration parent) {
158         return new NestedConfiguration(chooser, parent);
159     }
160 
161     @Override
162     @Nullable
getTheme()163     public String getTheme() {
164         // Never overridden: this is a static attribute of a layout, not something which
165         // varies by configuration or at runtime
166         return mParent.getTheme();
167     }
168 
169     @Override
setTheme(String theme)170     public void setTheme(String theme) {
171         // Never overridden
172         mParent.setTheme(theme);
173     }
174 
175     /**
176      * Sets whether the locale should be overridden by this configuration
177      *
178      * @param override if true, override the inherited value
179      */
setOverrideLocale(boolean override)180     public void setOverrideLocale(boolean override) {
181         mOverride |= CFG_LOCALE;
182     }
183 
184     /**
185      * Returns true if the locale is overridden
186      *
187      * @return true if the locale is overridden
188      */
isOverridingLocale()189     public final boolean isOverridingLocale() {
190         return (mOverride & CFG_LOCALE) != 0;
191     }
192 
193     @Override
194     @NonNull
getLocale()195     public Locale getLocale() {
196         if (isOverridingLocale()) {
197             return super.getLocale();
198         } else {
199             return mParent.getLocale();
200         }
201     }
202 
203     @Override
setLocale(@onNull Locale locale, boolean skipSync)204     public void setLocale(@NonNull Locale locale, boolean skipSync) {
205         if (isOverridingLocale()) {
206             super.setLocale(locale, skipSync);
207         } else {
208             mParent.setLocale(locale, skipSync);
209         }
210     }
211 
212     /**
213      * Sets whether the rendering target should be overridden by this configuration
214      *
215      * @param override if true, override the inherited value
216      */
setOverrideTarget(boolean override)217     public void setOverrideTarget(boolean override) {
218         mOverride |= CFG_TARGET;
219     }
220 
221     /**
222      * Returns true if the target is overridden
223      *
224      * @return true if the target is overridden
225      */
isOverridingTarget()226     public final boolean isOverridingTarget() {
227         return (mOverride & CFG_TARGET) != 0;
228     }
229 
230     @Override
231     @Nullable
getTarget()232     public IAndroidTarget getTarget() {
233         if (isOverridingTarget()) {
234             return super.getTarget();
235         } else {
236             return mParent.getTarget();
237         }
238     }
239 
240     @Override
setTarget(IAndroidTarget target, boolean skipSync)241     public void setTarget(IAndroidTarget target, boolean skipSync) {
242         if (isOverridingTarget()) {
243             super.setTarget(target, skipSync);
244         } else {
245             mParent.setTarget(target, skipSync);
246         }
247     }
248 
249     /**
250      * Sets whether the device should be overridden by this configuration
251      *
252      * @param override if true, override the inherited value
253      */
setOverrideDevice(boolean override)254     public void setOverrideDevice(boolean override) {
255         mOverride |= CFG_DEVICE;
256     }
257 
258     /**
259      * Returns true if the device is overridden
260      *
261      * @return true if the device is overridden
262      */
isOverridingDevice()263     public final boolean isOverridingDevice() {
264         return (mOverride & CFG_DEVICE) != 0;
265     }
266 
267     @Override
268     @Nullable
getDevice()269     public Device getDevice() {
270         if (isOverridingDevice()) {
271             return super.getDevice();
272         } else {
273             return mParent.getDevice();
274         }
275     }
276 
277     @Override
setDevice(Device device, boolean skipSync)278     public void setDevice(Device device, boolean skipSync) {
279         if (isOverridingDevice()) {
280             super.setDevice(device, skipSync);
281         } else {
282             mParent.setDevice(device, skipSync);
283         }
284     }
285 
286     /**
287      * Sets whether the device state should be overridden by this configuration
288      *
289      * @param override if true, override the inherited value
290      */
setOverrideDeviceState(boolean override)291     public void setOverrideDeviceState(boolean override) {
292         mOverride |= CFG_DEVICE_STATE;
293     }
294 
295     /**
296      * Returns true if the device state is overridden
297      *
298      * @return true if the device state is overridden
299      */
isOverridingDeviceState()300     public final boolean isOverridingDeviceState() {
301         return (mOverride & CFG_DEVICE_STATE) != 0;
302     }
303 
304     @Override
305     @Nullable
getDeviceState()306     public State getDeviceState() {
307         if (isOverridingDeviceState()) {
308             return super.getDeviceState();
309         } else {
310             State state = mParent.getDeviceState();
311             if (isOverridingDevice()) {
312                 // If the device differs, I need to look up a suitable equivalent state
313                 // on our device
314                 if (state != null) {
315                     Device device = super.getDevice();
316                     if (device != null) {
317                         return device.getState(state.getName());
318                     }
319                 }
320             }
321 
322             return state;
323         }
324     }
325 
326     @Override
setDeviceState(State state, boolean skipSync)327     public void setDeviceState(State state, boolean skipSync) {
328         if (isOverridingDeviceState()) {
329             super.setDeviceState(state, skipSync);
330         } else {
331             if (isOverridingDevice()) {
332                 Device device = super.getDevice();
333                 if (device != null) {
334                     State equivalentState = device.getState(state.getName());
335                     if (equivalentState != null) {
336                         state = equivalentState;
337                     }
338                 }
339             }
340             mParent.setDeviceState(state, skipSync);
341         }
342     }
343 
344     /**
345      * Sets whether the night mode should be overridden by this configuration
346      *
347      * @param override if true, override the inherited value
348      */
setOverrideNightMode(boolean override)349     public void setOverrideNightMode(boolean override) {
350         mOverride |= CFG_NIGHT_MODE;
351     }
352 
353     /**
354      * Returns true if the night mode is overridden
355      *
356      * @return true if the night mode is overridden
357      */
isOverridingNightMode()358     public final boolean isOverridingNightMode() {
359         return (mOverride & CFG_NIGHT_MODE) != 0;
360     }
361 
362     @Override
363     @NonNull
getNightMode()364     public NightMode getNightMode() {
365         if (isOverridingNightMode()) {
366             return super.getNightMode();
367         } else {
368             return mParent.getNightMode();
369         }
370     }
371 
372     @Override
setNightMode(@onNull NightMode night, boolean skipSync)373     public void setNightMode(@NonNull NightMode night, boolean skipSync) {
374         if (isOverridingNightMode()) {
375             super.setNightMode(night, skipSync);
376         } else {
377             mParent.setNightMode(night, skipSync);
378         }
379     }
380 
381     /**
382      * Sets whether the UI mode should be overridden by this configuration
383      *
384      * @param override if true, override the inherited value
385      */
setOverrideUiMode(boolean override)386     public void setOverrideUiMode(boolean override) {
387         mOverride |= CFG_UI_MODE;
388     }
389 
390     /**
391      * Returns true if the UI mode is overridden
392      *
393      * @return true if the UI mode is overridden
394      */
isOverridingUiMode()395     public final boolean isOverridingUiMode() {
396         return (mOverride & CFG_UI_MODE) != 0;
397     }
398 
399     @Override
400     @NonNull
getUiMode()401     public UiMode getUiMode() {
402         if (isOverridingUiMode()) {
403             return super.getUiMode();
404         } else {
405             return mParent.getUiMode();
406         }
407     }
408 
409     @Override
setUiMode(@onNull UiMode uiMode, boolean skipSync)410     public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) {
411         if (isOverridingUiMode()) {
412             super.setUiMode(uiMode, skipSync);
413         } else {
414             mParent.setUiMode(uiMode, skipSync);
415         }
416     }
417 
418     /**
419      * Returns the configuration this {@linkplain NestedConfiguration} is
420      * inheriting from
421      *
422      * @return the configuration this configuration is inheriting from
423      */
424     @NonNull
getParent()425     public Configuration getParent() {
426         return mParent;
427     }
428 
429     @Override
430     @Nullable
getActivity()431     public String getActivity() {
432         return mParent.getActivity();
433     }
434 
435     @Override
setActivity(String activity)436     public void setActivity(String activity) {
437         super.setActivity(activity);
438     }
439 
440     /**
441      * Returns a computed display name (ignoring the value stored by
442      * {@link #setDisplayName(String)}) by looking at the override flags
443      * and picking a suitable name.
444      *
445      * @return a suitable display name
446      */
447     @Nullable
computeDisplayName()448     public String computeDisplayName() {
449         return computeDisplayName(mOverride, this);
450     }
451 
452     /**
453      * Computes a display name for the given configuration, using the given
454      * override flags (which correspond to the {@code CFG_} constants in
455      * {@link ConfigurationClient}
456      *
457      * @param flags the override bitmask
458      * @param configuration the configuration to fetch values from
459      * @return a suitable display name
460      */
461     @Nullable
computeDisplayName(int flags, @NonNull Configuration configuration)462     public static String computeDisplayName(int flags, @NonNull Configuration configuration) {
463         if ((flags & CFG_LOCALE) != 0) {
464             return ConfigurationChooser.getLocaleLabel(configuration.mConfigChooser,
465                     configuration.getLocale(), false);
466         }
467 
468         if ((flags & CFG_TARGET) != 0) {
469             return ConfigurationChooser.getRenderingTargetLabel(configuration.getTarget(), false);
470         }
471 
472         if ((flags & CFG_DEVICE) != 0) {
473             return ConfigurationChooser.getDeviceLabel(configuration.getDevice(), true);
474         }
475 
476         if ((flags & CFG_DEVICE_STATE) != 0) {
477             State deviceState = configuration.getDeviceState();
478             if (deviceState != null) {
479                 return deviceState.getName();
480             }
481         }
482 
483         if ((flags & CFG_NIGHT_MODE) != 0) {
484             return configuration.getNightMode().getLongDisplayValue();
485         }
486 
487         if ((flags & CFG_UI_MODE) != 0) {
488             configuration.getUiMode().getLongDisplayValue();
489         }
490 
491         return null;
492     }
493 
494     @Override
toString()495     public String toString() {
496         return Objects.toStringHelper(this.getClass())
497                 .add("parent", mParent.getDisplayName())          //$NON-NLS-1$
498                 .add("display", getDisplayName())                 //$NON-NLS-1$
499                 .add("overrideLocale", isOverridingLocale())           //$NON-NLS-1$
500                 .add("overrideTarget", isOverridingTarget())           //$NON-NLS-1$
501                 .add("overrideDevice", isOverridingDevice())           //$NON-NLS-1$
502                 .add("overrideDeviceState", isOverridingDeviceState()) //$NON-NLS-1$
503                 .add("persistent", toPersistentString())          //$NON-NLS-1$
504                 .toString();
505     }
506 }
507