blob: 9df596c50864592b549d33e52cfa4ec065d0346c [file] [log] [blame]
/*
* 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;
}
}