| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.tools.idea.configurations; |
| |
| 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.State; |
| import com.android.tools.idea.rendering.Locale; |
| import com.google.common.base.Objects; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** |
| * An {@linkplain NestedConfiguration} 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. |
| * <p/> |
| * Unlike a {@link VaryingConfiguration}, a {@linkplain NestedConfiguration} |
| * will always return the same overridden value, regardless of the inherited |
| * value. |
| * <p/> |
| * For example, an {@linkplain NestedConfiguration} may fix the locale to always |
| * be "en", but otherwise inherit everything else. |
| */ |
| public class NestedConfiguration extends Configuration implements ConfigurationListener { |
| /** |
| * The configuration we are inheriting non-overridden values from |
| */ |
| protected Configuration myParent; |
| |
| /** |
| * Bitmask of attributes to be overridden in this configuration |
| */ |
| private int myOverride; |
| |
| /** |
| * Constructs a new {@linkplain NestedConfiguration}. |
| * Construct via {@link #create(Configuration)}. |
| * |
| * @param configuration the configuration to inherit from |
| */ |
| protected NestedConfiguration(@NotNull Configuration configuration) { |
| super(configuration.getConfigurationManager(), configuration.getFile(), configuration.getEditedConfig()); |
| myParent = configuration; |
| myFullConfig.set(myParent.myFullConfig); |
| |
| myParent.addListener(this); |
| } |
| |
| /** |
| * Returns the override flags for this configuration. Corresponds to |
| * the {@code CFG_} flags in {@link ConfigurationListener}. |
| * |
| * @return the bitmask |
| */ |
| public int getOverrideFlags() { |
| return myOverride; |
| } |
| |
| /** |
| * Creates a new {@linkplain NestedConfiguration} that has the same overriding |
| * attributes as the given other {@linkplain NestedConfiguration}, and gets |
| * its values from the given {@linkplain Configuration}. |
| * |
| * @param other the configuration to copy overrides from |
| * @param values the configuration to copy values from |
| * @param parent the parent to tie the configuration to for inheriting values |
| * @return a new configuration |
| */ |
| @NotNull |
| public static NestedConfiguration create(@NotNull NestedConfiguration other, |
| @NotNull Configuration values, |
| @NotNull Configuration parent) { |
| NestedConfiguration configuration = new NestedConfiguration(parent); |
| initFrom(configuration, other, values); |
| return configuration; |
| } |
| |
| /** |
| * Initializes a new {@linkplain NestedConfiguration} with the overriding |
| * attributes as the given other {@linkplain NestedConfiguration}, and gets |
| * its values from the given {@linkplain Configuration}. |
| * |
| * @param configuration the configuration to initialize |
| * @param other the configuration to copy overrides from |
| * @param values the configuration to copy values from |
| */ |
| protected static void initFrom(NestedConfiguration configuration, NestedConfiguration other, Configuration values) { |
| // TODO: Rewrite to use the clone method! |
| configuration.startBulkEditing(); |
| configuration.myOverride = other.myOverride; |
| configuration.setDisplayName(values.getDisplayName()); |
| String activity = values.getActivity(); |
| if (activity != null) { |
| configuration.setActivity(activity); |
| } |
| |
| if (configuration.isOverridingLocale()) { |
| configuration.setLocale(values.getLocale()); |
| } |
| if (configuration.isOverridingTarget()) { |
| IAndroidTarget target = values.getTarget(); |
| if (target != null) { |
| configuration.setTarget(target); |
| } |
| } |
| if (configuration.isOverridingDevice()) { |
| Device device = values.getDevice(); |
| if (device != null) { |
| configuration.setDevice(device, true); |
| } |
| } |
| if (configuration.isOverridingDeviceState()) { |
| State deviceState = values.getDeviceState(); |
| if (deviceState != null) { |
| configuration.setDeviceState(deviceState); |
| } |
| } |
| if (configuration.isOverridingNightMode()) { |
| configuration.setNightMode(values.getNightMode()); |
| } |
| if (configuration.isOverridingUiMode()) { |
| configuration.setUiMode(values.getUiMode()); |
| } |
| configuration.finishBulkEditing(); |
| } |
| |
| /** |
| * Sets the parent configuration that this configuration is inheriting from. |
| * |
| * @param parent the parent configuration |
| */ |
| public void setParent(@NotNull Configuration parent) { |
| myParent = parent; |
| } |
| |
| /** |
| * Creates a new {@linkplain Configuration} which inherits values from the |
| * given parent {@linkplain Configuration}, possibly overriding some as |
| * well. |
| * |
| * @param parent the configuration to inherit values from |
| * @return a new configuration |
| */ |
| @NotNull |
| public static NestedConfiguration create(@NotNull Configuration parent) { |
| return new NestedConfiguration(parent); |
| } |
| |
| @Override |
| @Nullable |
| public String getTheme() { |
| if (isOverridingTarget()) { |
| return super.getTheme(); |
| } else { |
| return myParent.getTheme(); |
| } |
| } |
| |
| @Override |
| public void setTheme(@Nullable String theme) { |
| if (isOverridingTarget()) { |
| super.setTheme(theme); |
| } else { |
| myParent.setTheme(theme); |
| } |
| } |
| |
| /** |
| * Sets whether the locale should be overridden by this configuration |
| * |
| * @param override if true, override the inherited value |
| */ |
| public void setOverrideLocale(boolean override) { |
| if (override) { |
| myOverride |= CFG_LOCALE; |
| } else { |
| myOverride &= ~CFG_LOCALE; |
| } |
| } |
| |
| /** |
| * Returns true if the locale is overridden |
| * |
| * @return true if the locale is overridden |
| */ |
| public final boolean isOverridingLocale() { |
| return (myOverride & CFG_LOCALE) != 0; |
| } |
| |
| @Override |
| @NotNull |
| public Locale getLocale() { |
| if (isOverridingLocale()) { |
| return super.getLocale(); |
| } |
| else { |
| return myParent.getLocale(); |
| } |
| } |
| |
| @Override |
| public void setLocale(@NotNull Locale locale) { |
| if (isOverridingLocale()) { |
| super.setLocale(locale); |
| } |
| else { |
| myParent.setLocale(locale); |
| } |
| } |
| |
| /** |
| * Sets whether the rendering target should be overridden by this configuration |
| * |
| * @param override if true, override the inherited value |
| */ |
| public void setOverrideTarget(boolean override) { |
| if (override) { |
| myOverride |= CFG_TARGET; |
| } else { |
| myOverride &= ~CFG_TARGET; |
| } |
| } |
| |
| /** |
| * Returns true if the target is overridden |
| * |
| * @return true if the target is overridden |
| */ |
| public final boolean isOverridingTarget() { |
| return (myOverride & CFG_TARGET) != 0; |
| } |
| |
| @Override |
| @Nullable |
| public IAndroidTarget getTarget() { |
| if (isOverridingTarget()) { |
| return super.getTarget(); |
| } |
| else { |
| return myParent.getTarget(); |
| } |
| } |
| |
| @Override |
| public void setTarget(IAndroidTarget target) { |
| if (isOverridingTarget()) { |
| super.setTarget(target); |
| } |
| else { |
| myParent.setTarget(target); |
| } |
| } |
| |
| /** |
| * Sets whether the device should be overridden by this configuration |
| * |
| * @param override if true, override the inherited value |
| */ |
| public void setOverrideDevice(boolean override) { |
| if (override) { |
| myOverride |= CFG_DEVICE; |
| } else { |
| myOverride &= ~CFG_DEVICE; |
| } |
| } |
| |
| /** |
| * Returns true if the device is overridden |
| * |
| * @return true if the device is overridden |
| */ |
| public final boolean isOverridingDevice() { |
| return (myOverride & CFG_DEVICE) != 0; |
| } |
| |
| @Override |
| @Nullable |
| public Device getDevice() { |
| if (isOverridingDevice()) { |
| return super.getDevice(); |
| } |
| else { |
| return myParent.getDevice(); |
| } |
| } |
| |
| @Override |
| public void setDevice(Device device, boolean preserveState) { |
| if (isOverridingDevice()) { |
| super.setDevice(device, preserveState); |
| } |
| else { |
| myParent.setDevice(device, preserveState); |
| } |
| } |
| |
| /** |
| * Sets whether the device state should be overridden by this configuration |
| * |
| * @param override if true, override the inherited value |
| */ |
| public void setOverrideDeviceState(boolean override) { |
| if (override) { |
| myOverride |= CFG_DEVICE_STATE; |
| } else { |
| myOverride &= ~CFG_DEVICE_STATE; |
| } |
| } |
| |
| /** |
| * Returns true if the device state is overridden |
| * |
| * @return true if the device state is overridden |
| */ |
| public final boolean isOverridingDeviceState() { |
| return (myOverride & CFG_DEVICE_STATE) != 0; |
| } |
| |
| @Override |
| @Nullable |
| public State getDeviceState() { |
| if (isOverridingDeviceState()) { |
| return super.getDeviceState(); |
| } |
| else { |
| State state = myParent.getDeviceState(); |
| if (isOverridingDevice()) { |
| // If the device differs, I need to look up a suitable equivalent state |
| // on our device |
| if (state != null) { |
| Device device = super.getDevice(); |
| if (device != null) { |
| String name = state.getName(); |
| state = device.getState(name); |
| if (state != null) { |
| return state; |
| } |
| // No such state in this screen |
| // Try to find a *similar* one. For example, |
| // the parent may be "Landscape" and this device |
| // may have "Landscape,Closed" and "Landscape,Open" |
| // as is the case with device "3.2in HGVA slider (ADP1)". |
| int nameLen = name.length(); |
| for (State s : device.getAllStates()) { |
| String n = s.getName(); |
| if (n.regionMatches(0, name, 0, Math.min(nameLen, n.length()))) { |
| return s; |
| } |
| } |
| |
| return device.getDefaultState(); |
| } |
| } |
| } |
| |
| return state; |
| } |
| } |
| |
| @Override |
| public void setDeviceState(State state) { |
| if (isOverridingDeviceState()) { |
| super.setDeviceState(state); |
| } |
| else { |
| if (isOverridingDevice()) { |
| Device device = super.getDevice(); |
| if (device != null) { |
| State equivalentState = device.getState(state.getName()); |
| if (equivalentState != null) { |
| state = equivalentState; |
| } |
| } |
| } |
| myParent.setDeviceState(state); |
| } |
| } |
| |
| /** |
| * Sets whether the night mode should be overridden by this configuration |
| * |
| * @param override if true, override the inherited value |
| */ |
| public void setOverrideNightMode(boolean override) { |
| if (override) { |
| myOverride |= CFG_NIGHT_MODE; |
| } else { |
| myOverride &= ~CFG_NIGHT_MODE; |
| } |
| } |
| |
| /** |
| * Returns true if the night mode is overridden |
| * |
| * @return true if the night mode is overridden |
| */ |
| public final boolean isOverridingNightMode() { |
| return (myOverride & CFG_NIGHT_MODE) != 0; |
| } |
| |
| @Override |
| @NotNull |
| public NightMode getNightMode() { |
| if (isOverridingNightMode()) { |
| return super.getNightMode(); |
| } |
| else { |
| return myParent.getNightMode(); |
| } |
| } |
| |
| @Override |
| public void setNightMode(@NotNull NightMode night) { |
| if (isOverridingNightMode()) { |
| super.setNightMode(night); |
| } |
| else { |
| myParent.setNightMode(night); |
| } |
| } |
| |
| /** |
| * Sets whether the UI mode should be overridden by this configuration |
| * |
| * @param override if true, override the inherited value |
| */ |
| public void setOverrideUiMode(boolean override) { |
| if (override) { |
| myOverride |= CFG_UI_MODE; |
| } else { |
| myOverride &= ~CFG_UI_MODE; |
| } |
| } |
| |
| /** |
| * Returns true if the UI mode is overridden |
| * |
| * @return true if the UI mode is overridden |
| */ |
| public final boolean isOverridingUiMode() { |
| return (myOverride & CFG_UI_MODE) != 0; |
| } |
| |
| @Override |
| @NotNull |
| public UiMode getUiMode() { |
| if (isOverridingUiMode()) { |
| return super.getUiMode(); |
| } |
| else { |
| return myParent.getUiMode(); |
| } |
| } |
| |
| @Override |
| public void setUiMode(@NotNull UiMode uiMode) { |
| if (isOverridingUiMode()) { |
| super.setUiMode(uiMode); |
| } |
| else { |
| myParent.setUiMode(uiMode); |
| } |
| } |
| |
| /** |
| * Returns the configuration this {@linkplain NestedConfiguration} is |
| * inheriting from |
| * |
| * @return the configuration this configuration is inheriting from |
| */ |
| @NotNull |
| public Configuration getParent() { |
| return myParent; |
| } |
| |
| @Override |
| @Nullable |
| public String getActivity() { |
| return myParent.getActivity(); |
| } |
| |
| @Override |
| public void setActivity(String activity) { |
| super.setActivity(activity); |
| } |
| |
| /** |
| * Returns a computed display name (ignoring the value stored by |
| * {@link #setDisplayName(String)}) by looking at the override flags |
| * and picking a suitable name. |
| * |
| * @return a suitable display name |
| */ |
| @Nullable |
| public String computeDisplayName() { |
| return computeDisplayName(myOverride, this); |
| } |
| |
| /** |
| * Computes a display name for the given configuration, using the given |
| * override flags (which correspond to the {@code CFG_} constants in |
| * {@link ConfigurationListener} |
| * |
| * @param flags the override bitmask |
| * @param configuration the configuration to fetch values from |
| * @return a suitable display name |
| */ |
| @Nullable |
| public static String computeDisplayName(int flags, @NotNull Configuration configuration) { |
| if ((flags & CFG_LOCALE) != 0) { |
| return LocaleMenuAction.getLocaleLabel(configuration.getLocale(), false); |
| } |
| |
| if ((flags & CFG_TARGET) != 0) { |
| return TargetMenuAction.getRenderingTargetLabel(configuration.getTarget(), false); |
| } |
| |
| if ((flags & CFG_DEVICE) != 0) { |
| return DeviceMenuAction.getDeviceLabel(configuration.getDevice(), true); |
| } |
| |
| if ((flags & CFG_DEVICE_STATE) != 0) { |
| State deviceState = configuration.getDeviceState(); |
| if (deviceState != null) { |
| return deviceState.getName(); |
| } |
| } |
| |
| if ((flags & CFG_NIGHT_MODE) != 0) { |
| return configuration.getNightMode().getLongDisplayValue(); |
| } |
| |
| if ((flags & CFG_UI_MODE) != 0) { |
| configuration.getUiMode().getLongDisplayValue(); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public String toString() { |
| return Objects.toStringHelper(this.getClass()).add("parent", myParent.getDisplayName()) |
| .add("display", getDisplayName()) |
| .add("overrideLocale", isOverridingLocale()) |
| .add("overrideTarget", isOverridingTarget()) |
| .add("overrideDevice", isOverridingDevice()) |
| .add("overrideDeviceState", isOverridingDeviceState()) |
| .add("inherited", super.toString()) |
| .toString(); |
| } |
| |
| |
| @Override |
| public void dispose() { |
| myParent.removeListener(this); |
| } |
| |
| @Override |
| public boolean changed(int flags) { |
| // Mask out the flags that we are overriding; those changes do not affect us |
| flags &= ~myOverride; |
| if (flags != 0) { |
| updated(flags); |
| } |
| return true; |
| } |
| } |