diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java new file mode 100644 index 0000000..c6e6407 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.editors.layout.configuration; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.Capability; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.resources.Density; +import com.android.resources.NightMode; +import com.android.resources.UiMode; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.devices.Device; +import com.android.sdklib.devices.Hardware; +import com.android.sdklib.devices.Screen; +import com.android.sdklib.devices.State; + +import java.util.List; + +/** + * An {@linkplain VaryingConfiguration} is a {@link Configuration} which + * inherits all of its values from a different configuration, except for one or + * more attributes where it overrides a custom value, and the overridden value + * will always <b>differ</b> from the inherited value! + * <p> + * For example, a {@linkplain VaryingConfiguration} may state that it + * overrides the locale, and if the inherited locale is "en", then the returned + * locale from the {@linkplain VaryingConfiguration} may be for example "nb", + * but never "en". + * <p> + * The configuration will attempt to make its changed inherited value to be as + * different as possible from the inherited value. Thus, a configuration which + * overrides the device will probably return a phone-sized screen if the + * inherited device is a tablet, or vice versa. + */ +public class VaryingConfiguration extends NestedConfiguration { + /** Variation version; see {@link #setVariation(int)} */ + private int mVariation; + + /** Variation version count; see {@link #setVariationCount(int)} */ + private int mVariationCount; + + /** Bitmask of attributes to be varied/alternated from the parent */ + private int mAlternate; + + /** + * Constructs a new {@linkplain VaryingConfiguration}. + * Construct via + * + * @param chooser the associated chooser + * @param configuration the configuration to inherit from + */ + private VaryingConfiguration( + @NonNull ConfigurationChooser chooser, + @NonNull Configuration configuration) { + super(chooser, configuration); + } + + /** + * Creates a new {@linkplain Configuration} which inherits values from the + * given parent {@linkplain Configuration}, possibly overriding some as + * well. + * + * @param chooser the associated chooser + * @param parent the configuration to inherit values from + * @return a new configuration + */ + @NonNull + public static VaryingConfiguration create(@NonNull ConfigurationChooser chooser, + @NonNull Configuration parent) { + return new VaryingConfiguration(chooser, parent); + } + + /** + * Creates a new {@linkplain VaryingConfiguration} that has the same overriding + * attributes as the given other {@linkplain VaryingConfiguration}. + * + * @param other the configuration to copy overrides from + * @param parent the parent to tie the configuration to for inheriting values + * @return a new configuration + */ + @NonNull + public static VaryingConfiguration create( + @NonNull VaryingConfiguration other, + @NonNull Configuration parent) { + VaryingConfiguration configuration = + new VaryingConfiguration(other.mConfigChooser, parent); + initFrom(configuration, other, other, false); + configuration.mAlternate = other.mAlternate; + configuration.mVariation = other.mVariation; + configuration.mVariationCount = other.mVariationCount; + configuration.syncFolderConfig(); + + return configuration; + } + + /** + * Returns the alternate flags for this configuration. Corresponds to + * the {@code CFG_} flags in {@link ConfigurationClient}. + * + * @return the bitmask + */ + public int getAlternateFlags() { + return mAlternate; + } + + @Override + public void syncFolderConfig() { + super.syncFolderConfig(); + updateDisplayName(); + } + + /** + * Sets the variation version for this + * {@linkplain VaryingConfiguration}. There might be multiple + * {@linkplain VaryingConfiguration} instances inheriting from a + * {@link Configuration}. The variation version allows them to choose + * different complementing values, so they don't all flip to the same other + * (out of multiple choices) value. The {@link #setVariationCount(int)} + * value can be used to determine how to partition the buckets of values. + * Also updates the variation count if necessary. + * + * @param variation variation version + */ + public void setVariation(int variation) { + mVariation = variation; + mVariationCount = Math.max(mVariationCount, variation + 1); + } + + /** + * Sets the number of {@link VaryingConfiguration} variations mapped + * to the same parent configuration as this one. See + * {@link #setVariation(int)} for details. + * + * @param count the total number of variation versions + */ + public void setVariationCount(int count) { + mVariationCount = count; + } + + /** + * Updates the display name in this configuration based on the values and override settings + */ + public void updateDisplayName() { + setDisplayName(computeDisplayName()); + } + + @Override + @NonNull + public Locale getLocale() { + if (isOverridingLocale()) { + return super.getLocale(); + } + Locale locale = mParent.getLocale(); + if (isAlternatingLocale() && locale != null) { + List<Locale> locales = mConfigChooser.getLocaleList(); + for (Locale l : locales) { + // TODO: Try to be smarter about which one we pick; for example, try + // to pick a language that is substantially different from the inherited + // language, such as either with the strings of the largest or shortest + // length, or perhaps based on some geography or population metrics + if (!l.equals(locale)) { + locale = l; + break; + } + } + } + + return locale; + } + + @Override + @Nullable + public IAndroidTarget getTarget() { + if (isOverridingTarget()) { + return super.getTarget(); + } + IAndroidTarget target = mParent.getTarget(); + if (isAlternatingTarget() && target != null) { + List<IAndroidTarget> targets = mConfigChooser.getTargetList(); + if (!targets.isEmpty()) { + // Pick a different target: if you're showing the most recent render target, + // then pick the lowest supported target, and vice versa + IAndroidTarget mostRecent = targets.get(targets.size() - 1); + if (target.equals(mostRecent)) { + // Find oldest supported + ManifestInfo info = ManifestInfo.get(mConfigChooser.getProject()); + int minSdkVersion = info.getMinSdkVersion(); + for (IAndroidTarget t : targets) { + if (t.getVersion().getApiLevel() >= minSdkVersion) { + target = t; + break; + } + } + } else { + target = mostRecent; + } + } + } + + return target; + } + + // Cached values, key=parent's device, cached value=device + private Device mPrevParentDevice; + private Device mPrevDevice; + + @Override + @Nullable + public Device getDevice() { + if (isOverridingDevice()) { + return super.getDevice(); + } + Device device = mParent.getDevice(); + if (isAlternatingDevice() && device != null) { + if (device == mPrevParentDevice) { + return mPrevDevice; + } + + mPrevParentDevice = device; + + // Pick a different device + List<Device> devices = mConfigChooser.getDeviceList(); + + // Divide up the available devices into {@link #mVariationCount} + 1 buckets + // (the + 1 is for the bucket now taken up by the inherited value). + // Then assign buckets to each {@link #mVariation} version, and pick one + // from the bucket assigned to this current configuration's variation version. + + // I could just divide up the device list count, but that would treat a lot of + // very similar phones as having the same kind of variety as the 7" and 10" + // tablets which are sitting right next to each other in the device list. + // Instead, do this by screen size. + + + double smallest = 100; + double biggest = 1; + for (Device d : devices) { + double size = getScreenSize(d); + if (size < 0) { + continue; // no data + } + if (size >= biggest) { + biggest = size; + } + if (size <= smallest) { + smallest = size; + } + } + + int bucketCount = mVariationCount + 1; + double inchesPerBucket = (biggest - smallest) / bucketCount; + + double overriddenSize = getScreenSize(device); + int overriddenBucket = (int) ((overriddenSize - smallest) / inchesPerBucket); + int bucket = (mVariation < overriddenBucket) ? mVariation : mVariation + 1; + double from = inchesPerBucket * bucket + smallest; + double to = from + inchesPerBucket; + if (biggest - to < 0.1) { + to = biggest + 0.1; + } + + boolean canScaleNinePatch = supports(Capability.FIXED_SCALABLE_NINE_PATCH); + for (Device d : devices) { + double size = getScreenSize(d); + if (size >= from && size < to) { + if (!canScaleNinePatch) { + Density density = getDensity(d); + if (density == Density.TV || density == Density.LOW) { + continue; + } + } + + device = d; + break; + } + } + + mPrevDevice = device; + } + + return device; + } + + /** + * Returns the density of the given device + * + * @param device the device to check + * @return the density or null + */ + @Nullable + private static Density getDensity(@NonNull Device device) { + Hardware hardware = device.getDefaultHardware(); + if (hardware != null) { + Screen screen = hardware.getScreen(); + if (screen != null) { + return screen.getPixelDensity(); + } + } + + return null; + } + + /** + * Returns the diagonal length of the given device + * + * @param device the device to check + * @return the diagonal length or -1 + */ + private static double getScreenSize(@NonNull Device device) { + Hardware hardware = device.getDefaultHardware(); + if (hardware != null) { + Screen screen = hardware.getScreen(); + if (screen != null) { + return screen.getDiagonalLength(); + } + } + + return -1; + } + + @Override + @Nullable + public State getDeviceState() { + if (isOverridingDeviceState()) { + return super.getDeviceState(); + } + State state = mParent.getDeviceState(); + if (isAlternatingDeviceState() && state != null) { + State alternate = getNextDeviceState(state); + + return alternate; + } else { + if ((isAlternatingDevice() || isOverridingDevice()) && state != null) { + // If the device differs, I need to look up a suitable equivalent state + // on our device + Device device = getDevice(); + if (device != null) { + return device.getState(state.getName()); + } + } + + return state; + } + } + + @Override + @NonNull + public NightMode getNightMode() { + if (isOverridingNightMode()) { + return super.getNightMode(); + } + NightMode nightMode = mParent.getNightMode(); + if (isAlternatingNightMode() && nightMode != null) { + nightMode = nightMode == NightMode.NIGHT ? NightMode.NOTNIGHT : NightMode.NIGHT; + return nightMode; + } else { + return nightMode; + } + } + + @Override + @NonNull + public UiMode getUiMode() { + if (isOverridingUiMode()) { + return super.getUiMode(); + } + UiMode uiMode = mParent.getUiMode(); + if (isAlternatingUiMode() && uiMode != null) { + // TODO: Use manifest's supports screen to decide which are most relevant + // (as well as which available configuration qualifiers are present in the + // layout) + UiMode[] values = UiMode.values(); + uiMode = values[(uiMode.ordinal() + 1) % values.length]; + return uiMode; + } else { + return uiMode; + } + } + + @Override + @Nullable + public String computeDisplayName() { + return computeDisplayName(getOverrideFlags() | mAlternate, this); + } + + /** + * Sets whether the locale should be alternated by this configuration + * + * @param alternate if true, alternate the inherited value + */ + public void setAlternateLocale(boolean alternate) { + mAlternate |= CFG_LOCALE; + } + + /** + * Returns true if the locale is alternated + * + * @return true if the locale is alternated + */ + public final boolean isAlternatingLocale() { + return (mAlternate & CFG_LOCALE) != 0; + } + + /** + * Sets whether the rendering target should be alternated by this configuration + * + * @param alternate if true, alternate the inherited value + */ + public void setAlternateTarget(boolean alternate) { + mAlternate |= CFG_TARGET; + } + + /** + * Returns true if the target is alternated + * + * @return true if the target is alternated + */ + public final boolean isAlternatingTarget() { + return (mAlternate & CFG_TARGET) != 0; + } + + /** + * Sets whether the device should be alternated by this configuration + * + * @param alternate if true, alternate the inherited value + */ + public void setAlternateDevice(boolean alternate) { + mAlternate |= CFG_DEVICE; + } + + /** + * Returns true if the device is alternated + * + * @return true if the device is alternated + */ + public final boolean isAlternatingDevice() { + return (mAlternate & CFG_DEVICE) != 0; + } + + /** + * Sets whether the device state should be alternated by this configuration + * + * @param alternate if true, alternate the inherited value + */ + public void setAlternateDeviceState(boolean alternate) { + mAlternate |= CFG_DEVICE_STATE; + } + + /** + * Returns true if the device state is alternated + * + * @return true if the device state is alternated + */ + public final boolean isAlternatingDeviceState() { + return (mAlternate & CFG_DEVICE_STATE) != 0; + } + + /** + * Sets whether the night mode should be alternated by this configuration + * + * @param alternate if true, alternate the inherited value + */ + public void setAlternateNightMode(boolean alternate) { + mAlternate |= CFG_NIGHT_MODE; + } + + /** + * Returns true if the night mode is alternated + * + * @return true if the night mode is alternated + */ + public final boolean isAlternatingNightMode() { + return (mAlternate & CFG_NIGHT_MODE) != 0; + } + + /** + * Sets whether the UI mode should be alternated by this configuration + * + * @param alternate if true, alternate the inherited value + */ + public void setAlternateUiMode(boolean alternate) { + mAlternate |= CFG_UI_MODE; + } + + /** + * Returns true if the UI mode is alternated + * + * @return true if the UI mode is alternated + */ + public final boolean isAlternatingUiMode() { + return (mAlternate & CFG_UI_MODE) != 0; + } + +}
\ No newline at end of file |