• 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 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.rendering.api.Capability;
21 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
22 import com.android.resources.Density;
23 import com.android.resources.NightMode;
24 import com.android.resources.UiMode;
25 import com.android.sdklib.IAndroidTarget;
26 import com.android.sdklib.devices.Device;
27 import com.android.sdklib.devices.Hardware;
28 import com.android.sdklib.devices.Screen;
29 import com.android.sdklib.devices.State;
30 
31 import java.util.List;
32 
33 /**
34  * An {@linkplain VaryingConfiguration} is a {@link Configuration} which
35  * inherits all of its values from a different configuration, except for one or
36  * more attributes where it overrides a custom value, and the overridden value
37  * will always <b>differ</b> from the inherited value!
38  * <p>
39  * For example, a {@linkplain VaryingConfiguration} may state that it
40  * overrides the locale, and if the inherited locale is "en", then the returned
41  * locale from the {@linkplain VaryingConfiguration} may be for example "nb",
42  * but never "en".
43  * <p>
44  * The configuration will attempt to make its changed inherited value to be as
45  * different as possible from the inherited value. Thus, a configuration which
46  * overrides the device will probably return a phone-sized screen if the
47  * inherited device is a tablet, or vice versa.
48  */
49 public class VaryingConfiguration extends NestedConfiguration {
50     /** Variation version; see {@link #setVariation(int)} */
51     private int mVariation;
52 
53     /** Variation version count; see {@link #setVariationCount(int)} */
54     private int mVariationCount;
55 
56     /** Bitmask of attributes to be varied/alternated from the parent */
57     private int mAlternate;
58 
59     /**
60      * Constructs a new {@linkplain VaryingConfiguration}.
61      * Construct via
62      *
63      * @param chooser the associated chooser
64      * @param configuration the configuration to inherit from
65      */
VaryingConfiguration( @onNull ConfigurationChooser chooser, @NonNull Configuration configuration)66     private VaryingConfiguration(
67             @NonNull ConfigurationChooser chooser,
68             @NonNull Configuration configuration) {
69         super(chooser, configuration);
70     }
71 
72     /**
73      * Creates a new {@linkplain Configuration} which inherits values from the
74      * given parent {@linkplain Configuration}, possibly overriding some as
75      * well.
76      *
77      * @param chooser the associated chooser
78      * @param parent the configuration to inherit values from
79      * @return a new configuration
80      */
81     @NonNull
create(@onNull ConfigurationChooser chooser, @NonNull Configuration parent)82     public static VaryingConfiguration create(@NonNull ConfigurationChooser chooser,
83             @NonNull Configuration parent) {
84         return new VaryingConfiguration(chooser, parent);
85     }
86 
87     /**
88      * Creates a new {@linkplain VaryingConfiguration} that has the same overriding
89      * attributes as the given other {@linkplain VaryingConfiguration}.
90      *
91      * @param other the configuration to copy overrides from
92      * @param parent the parent to tie the configuration to for inheriting values
93      * @return a new configuration
94      */
95     @NonNull
create( @onNull VaryingConfiguration other, @NonNull Configuration parent)96     public static VaryingConfiguration create(
97             @NonNull VaryingConfiguration other,
98             @NonNull Configuration parent) {
99         VaryingConfiguration configuration =
100                 new VaryingConfiguration(other.mConfigChooser, parent);
101         initFrom(configuration, other, other, false);
102         configuration.mAlternate = other.mAlternate;
103         configuration.mVariation = other.mVariation;
104         configuration.mVariationCount = other.mVariationCount;
105         configuration.syncFolderConfig();
106 
107         return configuration;
108     }
109 
110     /**
111      * Returns the alternate flags for this configuration. Corresponds to
112      * the {@code CFG_} flags in {@link ConfigurationClient}.
113      *
114      * @return the bitmask
115      */
getAlternateFlags()116     public int getAlternateFlags() {
117         return mAlternate;
118     }
119 
120     @Override
syncFolderConfig()121     public void syncFolderConfig() {
122         super.syncFolderConfig();
123         updateDisplayName();
124     }
125 
126     /**
127      * Sets the variation version for this
128      * {@linkplain VaryingConfiguration}. There might be multiple
129      * {@linkplain VaryingConfiguration} instances inheriting from a
130      * {@link Configuration}. The variation version allows them to choose
131      * different complementing values, so they don't all flip to the same other
132      * (out of multiple choices) value. The {@link #setVariationCount(int)}
133      * value can be used to determine how to partition the buckets of values.
134      * Also updates the variation count if necessary.
135      *
136      * @param variation variation version
137      */
setVariation(int variation)138     public void setVariation(int variation) {
139         mVariation = variation;
140         mVariationCount = Math.max(mVariationCount, variation + 1);
141     }
142 
143     /**
144      * Sets the number of {@link VaryingConfiguration} variations mapped
145      * to the same parent configuration as this one. See
146      * {@link #setVariation(int)} for details.
147      *
148      * @param count the total number of variation versions
149      */
setVariationCount(int count)150     public void setVariationCount(int count) {
151         mVariationCount = count;
152     }
153 
154     /**
155      * Updates the display name in this configuration based on the values and override settings
156      */
updateDisplayName()157     public void updateDisplayName() {
158         setDisplayName(computeDisplayName());
159     }
160 
161     @Override
162     @NonNull
getLocale()163     public Locale getLocale() {
164         if (isOverridingLocale()) {
165             return super.getLocale();
166         }
167         Locale locale = mParent.getLocale();
168         if (isAlternatingLocale() && locale != null) {
169             List<Locale> locales = mConfigChooser.getLocaleList();
170             for (Locale l : locales) {
171                 // TODO: Try to be smarter about which one we pick; for example, try
172                 // to pick a language that is substantially different from the inherited
173                 // language, such as either with the strings of the largest or shortest
174                 // length, or perhaps based on some geography or population metrics
175                 if (!l.equals(locale)) {
176                     locale = l;
177                     break;
178                 }
179             }
180         }
181 
182         return locale;
183     }
184 
185     @Override
186     @Nullable
getTarget()187     public IAndroidTarget getTarget() {
188         if (isOverridingTarget()) {
189             return super.getTarget();
190         }
191         IAndroidTarget target = mParent.getTarget();
192         if (isAlternatingTarget() && target != null) {
193             List<IAndroidTarget> targets = mConfigChooser.getTargetList();
194             if (!targets.isEmpty()) {
195                 // Pick a different target: if you're showing the most recent render target,
196                 // then pick the lowest supported target, and vice versa
197                 IAndroidTarget mostRecent = targets.get(targets.size() - 1);
198                 if (target.equals(mostRecent)) {
199                     // Find oldest supported
200                     ManifestInfo info = ManifestInfo.get(mConfigChooser.getProject());
201                     int minSdkVersion = info.getMinSdkVersion();
202                     for (IAndroidTarget t : targets) {
203                         if (t.getVersion().getApiLevel() >= minSdkVersion) {
204                             target = t;
205                             break;
206                         }
207                     }
208                 } else {
209                     target = mostRecent;
210                 }
211             }
212         }
213 
214         return target;
215     }
216 
217     // Cached values, key=parent's device, cached value=device
218     private Device mPrevParentDevice;
219     private Device mPrevDevice;
220 
221     @Override
222     @Nullable
getDevice()223     public Device getDevice() {
224         if (isOverridingDevice()) {
225             return super.getDevice();
226         }
227         Device device = mParent.getDevice();
228         if (isAlternatingDevice() && device != null) {
229             if (device == mPrevParentDevice) {
230                 return mPrevDevice;
231             }
232 
233             mPrevParentDevice = device;
234 
235             // Pick a different device
236             List<Device> devices = mConfigChooser.getDeviceList();
237 
238             // Divide up the available devices into {@link #mVariationCount} + 1 buckets
239             // (the + 1 is for the bucket now taken up by the inherited value).
240             // Then assign buckets to each {@link #mVariation} version, and pick one
241             // from the bucket assigned to this current configuration's variation version.
242 
243             // I could just divide up the device list count, but that would treat a lot of
244             // very similar phones as having the same kind of variety as the 7" and 10"
245             // tablets which are sitting right next to each other in the device list.
246             // Instead, do this by screen size.
247 
248 
249             double smallest = 100;
250             double biggest = 1;
251             for (Device d : devices) {
252                 double size = getScreenSize(d);
253                 if (size < 0) {
254                     continue; // no data
255                 }
256                 if (size >= biggest) {
257                     biggest = size;
258                 }
259                 if (size <= smallest) {
260                     smallest = size;
261                 }
262             }
263 
264             int bucketCount = mVariationCount + 1;
265             double inchesPerBucket = (biggest - smallest) / bucketCount;
266 
267             double overriddenSize = getScreenSize(device);
268             int overriddenBucket = (int) ((overriddenSize - smallest) / inchesPerBucket);
269             int bucket = (mVariation < overriddenBucket) ? mVariation : mVariation + 1;
270             double from = inchesPerBucket * bucket + smallest;
271             double to = from + inchesPerBucket;
272             if (biggest - to < 0.1) {
273                 to = biggest + 0.1;
274             }
275 
276             boolean canScaleNinePatch = supports(Capability.FIXED_SCALABLE_NINE_PATCH);
277             for (Device d : devices) {
278                 double size = getScreenSize(d);
279                 if (size >= from && size < to) {
280                     if (!canScaleNinePatch) {
281                         Density density = getDensity(d);
282                         if (density == Density.TV || density == Density.LOW) {
283                             continue;
284                         }
285                     }
286 
287                     device = d;
288                     break;
289                 }
290             }
291 
292             mPrevDevice = device;
293         }
294 
295         return device;
296     }
297 
298     /**
299      * Returns the density of the given device
300      *
301      * @param device the device to check
302      * @return the density or null
303      */
304     @Nullable
getDensity(@onNull Device device)305     private static Density getDensity(@NonNull Device device) {
306         Hardware hardware = device.getDefaultHardware();
307         if (hardware != null) {
308             Screen screen = hardware.getScreen();
309             if (screen != null) {
310                 return screen.getPixelDensity();
311             }
312         }
313 
314         return null;
315     }
316 
317     /**
318      * Returns the diagonal length of the given device
319      *
320      * @param device the device to check
321      * @return the diagonal length or -1
322      */
getScreenSize(@onNull Device device)323     private static double getScreenSize(@NonNull Device device) {
324         Hardware hardware = device.getDefaultHardware();
325         if (hardware != null) {
326             Screen screen = hardware.getScreen();
327             if (screen != null) {
328                 return screen.getDiagonalLength();
329             }
330         }
331 
332         return -1;
333     }
334 
335     @Override
336     @Nullable
getDeviceState()337     public State getDeviceState() {
338         if (isOverridingDeviceState()) {
339             return super.getDeviceState();
340         }
341         State state = mParent.getDeviceState();
342         if (isAlternatingDeviceState() && state != null) {
343             State alternate = getNextDeviceState(state);
344 
345             return alternate;
346         } else {
347             if ((isAlternatingDevice() || isOverridingDevice()) && state != null) {
348                 // If the device differs, I need to look up a suitable equivalent state
349                 // on our device
350                 Device device = getDevice();
351                 if (device != null) {
352                     return device.getState(state.getName());
353                 }
354             }
355 
356             return state;
357         }
358     }
359 
360     @Override
361     @NonNull
getNightMode()362     public NightMode getNightMode() {
363         if (isOverridingNightMode()) {
364             return super.getNightMode();
365         }
366         NightMode nightMode = mParent.getNightMode();
367         if (isAlternatingNightMode() && nightMode != null) {
368             nightMode = nightMode == NightMode.NIGHT ? NightMode.NOTNIGHT : NightMode.NIGHT;
369             return nightMode;
370         } else {
371             return nightMode;
372         }
373     }
374 
375     @Override
376     @NonNull
getUiMode()377     public UiMode getUiMode() {
378         if (isOverridingUiMode()) {
379             return super.getUiMode();
380         }
381         UiMode uiMode = mParent.getUiMode();
382         if (isAlternatingUiMode() && uiMode != null) {
383             // TODO: Use manifest's supports screen to decide which are most relevant
384             // (as well as which available configuration qualifiers are present in the
385             // layout)
386             UiMode[] values = UiMode.values();
387             uiMode = values[(uiMode.ordinal() + 1) % values.length];
388             return uiMode;
389         } else {
390             return uiMode;
391         }
392     }
393 
394     @Override
395     @Nullable
computeDisplayName()396     public String computeDisplayName() {
397         return computeDisplayName(getOverrideFlags() | mAlternate, this);
398     }
399 
400     /**
401      * Sets whether the locale should be alternated by this configuration
402      *
403      * @param alternate if true, alternate the inherited value
404      */
setAlternateLocale(boolean alternate)405     public void setAlternateLocale(boolean alternate) {
406         mAlternate |= CFG_LOCALE;
407     }
408 
409     /**
410      * Returns true if the locale is alternated
411      *
412      * @return true if the locale is alternated
413      */
isAlternatingLocale()414     public final boolean isAlternatingLocale() {
415         return (mAlternate & CFG_LOCALE) != 0;
416     }
417 
418     /**
419      * Sets whether the rendering target should be alternated by this configuration
420      *
421      * @param alternate if true, alternate the inherited value
422      */
setAlternateTarget(boolean alternate)423     public void setAlternateTarget(boolean alternate) {
424         mAlternate |= CFG_TARGET;
425     }
426 
427     /**
428      * Returns true if the target is alternated
429      *
430      * @return true if the target is alternated
431      */
isAlternatingTarget()432     public final boolean isAlternatingTarget() {
433         return (mAlternate & CFG_TARGET) != 0;
434     }
435 
436     /**
437      * Sets whether the device should be alternated by this configuration
438      *
439      * @param alternate if true, alternate the inherited value
440      */
setAlternateDevice(boolean alternate)441     public void setAlternateDevice(boolean alternate) {
442         mAlternate |= CFG_DEVICE;
443     }
444 
445     /**
446      * Returns true if the device is alternated
447      *
448      * @return true if the device is alternated
449      */
isAlternatingDevice()450     public final boolean isAlternatingDevice() {
451         return (mAlternate & CFG_DEVICE) != 0;
452     }
453 
454     /**
455      * Sets whether the device state should be alternated by this configuration
456      *
457      * @param alternate if true, alternate the inherited value
458      */
setAlternateDeviceState(boolean alternate)459     public void setAlternateDeviceState(boolean alternate) {
460         mAlternate |= CFG_DEVICE_STATE;
461     }
462 
463     /**
464      * Returns true if the device state is alternated
465      *
466      * @return true if the device state is alternated
467      */
isAlternatingDeviceState()468     public final boolean isAlternatingDeviceState() {
469         return (mAlternate & CFG_DEVICE_STATE) != 0;
470     }
471 
472     /**
473      * Sets whether the night mode should be alternated by this configuration
474      *
475      * @param alternate if true, alternate the inherited value
476      */
setAlternateNightMode(boolean alternate)477     public void setAlternateNightMode(boolean alternate) {
478         mAlternate |= CFG_NIGHT_MODE;
479     }
480 
481     /**
482      * Returns true if the night mode is alternated
483      *
484      * @return true if the night mode is alternated
485      */
isAlternatingNightMode()486     public final boolean isAlternatingNightMode() {
487         return (mAlternate & CFG_NIGHT_MODE) != 0;
488     }
489 
490     /**
491      * Sets whether the UI mode should be alternated by this configuration
492      *
493      * @param alternate if true, alternate the inherited value
494      */
setAlternateUiMode(boolean alternate)495     public void setAlternateUiMode(boolean alternate) {
496         mAlternate |= CFG_UI_MODE;
497     }
498 
499     /**
500      * Returns true if the UI mode is alternated
501      *
502      * @return true if the UI mode is alternated
503      */
isAlternatingUiMode()504     public final boolean isAlternatingUiMode() {
505         return (mAlternate & CFG_UI_MODE) != 0;
506     }
507 
508 }