aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java
diff options
context:
space:
mode:
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.java509
1 files changed, 509 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 000000000..f472cd6b3
--- /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,509 @@
+/*
+ * 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.Collection;
+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
+ Collection<Device> devices = mConfigChooser.getDevices();
+
+ // 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