blob: c4253cddfd479389ae03a74916221faa36a6581d [file] [log] [blame]
/*
* 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 static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.api.Capability;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LayoutDirectionQualifier;
import com.android.ide.common.resources.configuration.LocaleQualifier;
import com.android.ide.common.resources.configuration.NightModeQualifier;
import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.resources.configuration.UiModeQualifier;
import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.Density;
import com.android.resources.LayoutDirection;
import com.android.resources.NightMode;
import com.android.resources.ScreenSize;
import com.android.resources.UiMode;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.State;
import com.android.utils.Pair;
import com.google.common.base.Objects;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
import java.util.List;
/**
* A {@linkplain Configuration} is a selection of device, orientation, theme,
* etc for use when rendering a layout.
*/
public class Configuration {
/** The {@link FolderConfiguration} in change flags or override flags */
public static final int CFG_FOLDER = 1 << 0;
/** The {@link Device} in change flags or override flags */
public static final int CFG_DEVICE = 1 << 1;
/** The {@link State} in change flags or override flags */
public static final int CFG_DEVICE_STATE = 1 << 2;
/** The theme in change flags or override flags */
public static final int CFG_THEME = 1 << 3;
/** The locale in change flags or override flags */
public static final int CFG_LOCALE = 1 << 4;
/** The rendering {@link IAndroidTarget} in change flags or override flags */
public static final int CFG_TARGET = 1 << 5;
/** The {@link NightMode} in change flags or override flags */
public static final int CFG_NIGHT_MODE = 1 << 6;
/** The {@link UiMode} in change flags or override flags */
public static final int CFG_UI_MODE = 1 << 7;
/** The {@link UiMode} in change flags or override flags */
public static final int CFG_ACTIVITY = 1 << 8;
/** References all attributes */
public static final int MASK_ALL = 0xFFFF;
/** Attributes which affect which best-layout-file selection */
public static final int MASK_FILE_ATTRS =
CFG_DEVICE|CFG_DEVICE_STATE|CFG_LOCALE|CFG_TARGET|CFG_NIGHT_MODE|CFG_UI_MODE;
/** Attributes which affect rendering appearance */
public static final int MASK_RENDERING = MASK_FILE_ATTRS|CFG_THEME;
/**
* Setting name for project-wide setting controlling rendering target and locale which
* is shared for all files
*/
public final static QualifiedName NAME_RENDER_STATE =
new QualifiedName(AdtPlugin.PLUGIN_ID, "render"); //$NON-NLS-1$
private final static String MARKER_FRAMEWORK = "-"; //$NON-NLS-1$
private final static String MARKER_PROJECT = "+"; //$NON-NLS-1$
private final static String SEP = ":"; //$NON-NLS-1$
private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
@NonNull
protected ConfigurationChooser mConfigChooser;
/** The {@link FolderConfiguration} representing the state of the UI controls */
@NonNull
protected final FolderConfiguration mFullConfig = new FolderConfiguration();
/** The {@link FolderConfiguration} being edited. */
@Nullable
protected FolderConfiguration mEditedConfig;
/** The target of the project of the file being edited. */
@Nullable
private IAndroidTarget mTarget;
/** The theme style to render with */
@Nullable
private String mTheme;
/** The device to render with */
@Nullable
private Device mDevice;
/** The device state */
@Nullable
private State mState;
/**
* The activity associated with the layout. This is just a cached value of
* the true value stored on the layout.
*/
@Nullable
private String mActivity;
/** The locale to use for this configuration */
@NonNull
private Locale mLocale = Locale.ANY;
/** UI mode */
@NonNull
private UiMode mUiMode = UiMode.NORMAL;
/** Night mode */
@NonNull
private NightMode mNightMode = NightMode.NOTNIGHT;
/** The display name */
private String mDisplayName;
/**
* Creates a new {@linkplain Configuration}
*
* @param chooser the associated chooser
*/
protected Configuration(@NonNull ConfigurationChooser chooser) {
mConfigChooser = chooser;
}
/**
* Sets the associated configuration chooser
*
* @param chooser the chooser
*/
void setChooser(@NonNull ConfigurationChooser chooser) {
// TODO: We should get rid of the binding between configurations
// and configuration choosers. This is currently needed because
// the choosers contain vital data such as the set of available
// rendering targets, the set of available locales etc, which
// also doesn't belong inside the configuration but is needed by it.
mConfigChooser = chooser;
}
/**
* Gets the associated configuration chooser
*
* @return the chooser
*/
@NonNull
ConfigurationChooser getChooser() {
return mConfigChooser;
}
/**
* Creates a new {@linkplain Configuration}
*
* @param chooser the associated chooser
* @return a new configuration
*/
@NonNull
public static Configuration create(@NonNull ConfigurationChooser chooser) {
return new Configuration(chooser);
}
/**
* Creates a configuration suitable for the given file
*
* @param base the base configuration to base the file configuration off of
* @param file the file to look up a configuration for
* @return a suitable configuration
*/
@NonNull
public static Configuration create(
@NonNull Configuration base,
@NonNull IFile file) {
Configuration configuration = copy(base);
ConfigurationChooser chooser = base.getChooser();
ProjectResources resources = chooser.getResources();
ConfigurationMatcher matcher = new ConfigurationMatcher(chooser, configuration, file,
resources, false);
ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
configuration.mEditedConfig = new FolderConfiguration();
configuration.mEditedConfig.set(resFolder.getConfiguration());
matcher.adaptConfigSelection(true /*needBestMatch*/);
configuration.syncFolderConfig();
return configuration;
}
/**
* Creates a new {@linkplain Configuration} that is a copy from a different configuration
*
* @param original the original to copy from
* @return a new configuration copied from the original
*/
@NonNull
public static Configuration copy(@NonNull Configuration original) {
Configuration copy = create(original.mConfigChooser);
copy.mFullConfig.set(original.mFullConfig);
if (original.mEditedConfig != null) {
copy.mEditedConfig = new FolderConfiguration();
copy.mEditedConfig.set(original.mEditedConfig);
}
copy.mTarget = original.getTarget();
copy.mTheme = original.getTheme();
copy.mDevice = original.getDevice();
copy.mState = original.getDeviceState();
copy.mActivity = original.getActivity();
copy.mLocale = original.getLocale();
copy.mUiMode = original.getUiMode();
copy.mNightMode = original.getNightMode();
copy.mDisplayName = original.getDisplayName();
return copy;
}
/**
* Returns the associated activity
*
* @return the activity
*/
@Nullable
public String getActivity() {
return mActivity;
}
/**
* Returns the chosen device.
*
* @return the chosen device
*/
@Nullable
public Device getDevice() {
return mDevice;
}
/**
* Returns the chosen device state
*
* @return the device state
*/
@Nullable
public State getDeviceState() {
return mState;
}
/**
* Returns the chosen locale
*
* @return the locale
*/
@NonNull
public Locale getLocale() {
return mLocale;
}
/**
* Returns the UI mode
*
* @return the UI mode
*/
@NonNull
public UiMode getUiMode() {
return mUiMode;
}
/**
* Returns the day/night mode
*
* @return the night mode
*/
@NonNull
public NightMode getNightMode() {
return mNightMode;
}
/**
* Returns the current theme style
*
* @return the theme style
*/
@Nullable
public String getTheme() {
return mTheme;
}
/**
* Returns the rendering target
*
* @return the target
*/
@Nullable
public IAndroidTarget getTarget() {
return mTarget;
}
/**
* Returns the display name to show for this configuration
*
* @return the display name, or null if none has been assigned
*/
@Nullable
public String getDisplayName() {
return mDisplayName;
}
/**
* Returns whether the configuration's theme is a project theme.
* <p/>
* The returned value is meaningless if {@link #getTheme()} returns
* <code>null</code>.
*
* @return true for project a theme, false for a framework theme
*/
public boolean isProjectTheme() {
String theme = getTheme();
if (theme != null) {
assert theme.startsWith(STYLE_RESOURCE_PREFIX)
|| theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
return ResourceHelper.isProjectStyle(theme);
}
return false;
}
/**
* Returns true if the current layout is locale-specific
*
* @return if this configuration represents a locale-specific layout
*/
public boolean isLocaleSpecificLayout() {
return mEditedConfig == null || mEditedConfig.getLocaleQualifier() != null;
}
/**
* Returns the full, complete {@link FolderConfiguration}
*
* @return the full configuration
*/
@NonNull
public FolderConfiguration getFullConfig() {
return mFullConfig;
}
/**
* Copies the full, complete {@link FolderConfiguration} into the given
* folder config instance.
*
* @param dest the {@link FolderConfiguration} instance to copy into
*/
public void copyFullConfig(FolderConfiguration dest) {
dest.set(mFullConfig);
}
/**
* Returns the edited {@link FolderConfiguration} (this is not a full
* configuration, so you can think of it as the "constraints" used by the
* {@link ConfigurationMatcher} to produce a full configuration.
*
* @return the constraints configuration
*/
@NonNull
public FolderConfiguration getEditedConfig() {
return mEditedConfig;
}
/**
* Sets the edited {@link FolderConfiguration} (this is not a full
* configuration, so you can think of it as the "constraints" used by the
* {@link ConfigurationMatcher} to produce a full configuration.
*
* @param editedConfig the constraints configuration
*/
public void setEditedConfig(@NonNull FolderConfiguration editedConfig) {
mEditedConfig = editedConfig;
}
/**
* Sets the associated activity
*
* @param activity the activity
*/
public void setActivity(String activity) {
mActivity = activity;
}
/**
* Sets the device
*
* @param device the device
* @param skipSync if true, don't sync folder configuration (typically because
* you are going to set other configuration parameters and you'll call
* {@link #syncFolderConfig()} once at the end)
*/
public void setDevice(Device device, boolean skipSync) {
mDevice = device;
if (!skipSync) {
syncFolderConfig();
}
}
/**
* Sets the device state
*
* @param state the device state
* @param skipSync if true, don't sync folder configuration (typically because
* you are going to set other configuration parameters and you'll call
* {@link #syncFolderConfig()} once at the end)
*/
public void setDeviceState(State state, boolean skipSync) {
mState = state;
if (!skipSync) {
syncFolderConfig();
}
}
/**
* Sets the locale
*
* @param locale the locale
* @param skipSync if true, don't sync folder configuration (typically because
* you are going to set other configuration parameters and you'll call
* {@link #syncFolderConfig()} once at the end)
*/
public void setLocale(@NonNull Locale locale, boolean skipSync) {
mLocale = locale;
if (!skipSync) {
syncFolderConfig();
}
}
/**
* Sets the rendering target
*
* @param target rendering target
* @param skipSync if true, don't sync folder configuration (typically because
* you are going to set other configuration parameters and you'll call
* {@link #syncFolderConfig()} once at the end)
*/
public void setTarget(IAndroidTarget target, boolean skipSync) {
mTarget = target;
if (!skipSync) {
syncFolderConfig();
}
}
/**
* Sets the display name to be shown for this configuration.
*
* @param displayName the new display name
*/
public void setDisplayName(@Nullable String displayName) {
mDisplayName = displayName;
}
/**
* Sets the night mode
*
* @param night the night mode
* @param skipSync if true, don't sync folder configuration (typically because
* you are going to set other configuration parameters and you'll call
* {@link #syncFolderConfig()} once at the end)
*/
public void setNightMode(@NonNull NightMode night, boolean skipSync) {
mNightMode = night;
if (!skipSync) {
syncFolderConfig();
}
}
/**
* Sets the UI mode
*
* @param uiMode the UI mode
* @param skipSync if true, don't sync folder configuration (typically because
* you are going to set other configuration parameters and you'll call
* {@link #syncFolderConfig()} once at the end)
*/
public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) {
mUiMode = uiMode;
if (!skipSync) {
syncFolderConfig();
}
}
/**
* Sets the theme style
*
* @param theme the theme
*/
public void setTheme(String theme) {
mTheme = theme;
checkThemePrefix();
}
/**
* Updates the folder configuration such that it reflects changes in
* configuration state such as the device orientation, the UI mode, the
* rendering target, etc.
*/
public void syncFolderConfig() {
Device device = getDevice();
if (device == null) {
return;
}
// get the device config from the device/state combos.
FolderConfiguration config = DeviceConfigHelper.getFolderConfig(getDeviceState());
// replace the config with the one from the device
mFullConfig.set(config);
// sync the selected locale
Locale locale = getLocale();
mFullConfig.setLocaleQualifier(locale.qualifier);
if (!locale.hasLanguage()) {
// Avoid getting the layout library if the locale doesn't have any language.
mFullConfig.setLayoutDirectionQualifier(
new LayoutDirectionQualifier(LayoutDirection.LTR));
} else {
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
AndroidTargetData targetData = currentSdk.getTargetData(getTarget());
if (targetData != null) {
LayoutLibrary layoutLib = targetData.getLayoutLibrary();
if (layoutLib != null) {
if (layoutLib.isRtl(locale.toLocaleId())) {
mFullConfig.setLayoutDirectionQualifier(
new LayoutDirectionQualifier(LayoutDirection.RTL));
} else {
mFullConfig.setLayoutDirectionQualifier(
new LayoutDirectionQualifier(LayoutDirection.LTR));
}
}
}
}
}
// Replace the UiMode with the selected one, if one is selected
UiMode uiMode = getUiMode();
if (uiMode != null) {
mFullConfig.setUiModeQualifier(new UiModeQualifier(uiMode));
}
// Replace the NightMode with the selected one, if one is selected
NightMode nightMode = getNightMode();
if (nightMode != null) {
mFullConfig.setNightModeQualifier(new NightModeQualifier(nightMode));
}
// replace the API level by the selection of the combo
IAndroidTarget target = getTarget();
if (target == null && mConfigChooser != null) {
target = mConfigChooser.getProjectTarget();
}
if (target != null) {
int apiLevel = target.getVersion().getApiLevel();
mFullConfig.setVersionQualifier(new VersionQualifier(apiLevel));
}
}
/**
* Creates a string suitable for persistence, which can be initialized back
* to a configuration via {@link #initialize(String)}
*
* @return a persistent string
*/
@NonNull
public String toPersistentString() {
StringBuilder sb = new StringBuilder(32);
Device device = getDevice();
if (device != null) {
sb.append(device.getName());
sb.append(SEP);
State state = getDeviceState();
if (state != null) {
sb.append(state.getName());
}
sb.append(SEP);
Locale locale = getLocale();
if (isLocaleSpecificLayout() && locale != null && locale.qualifier.hasLanguage()) {
// locale[0]/[1] can be null sometimes when starting Eclipse
sb.append(locale.qualifier.getLanguage());
sb.append(SEP_LOCALE);
if (locale.qualifier.hasRegion()) {
sb.append(locale.qualifier.getRegion());
}
}
sb.append(SEP);
// Need to escape the theme: if we write the full theme style, then
// we can end up with ":"'s in the string (as in @android:style/Theme) which
// can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}.
String theme = getTheme();
if (theme != null) {
String themeName = ResourceHelper.styleToTheme(theme);
if (theme.startsWith(STYLE_RESOURCE_PREFIX)) {
sb.append(MARKER_PROJECT);
} else if (theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
sb.append(MARKER_FRAMEWORK);
}
sb.append(themeName);
}
sb.append(SEP);
UiMode uiMode = getUiMode();
if (uiMode != null) {
sb.append(uiMode.getResourceValue());
}
sb.append(SEP);
NightMode nightMode = getNightMode();
if (nightMode != null) {
sb.append(nightMode.getResourceValue());
}
sb.append(SEP);
// We used to store the render target here in R9. Leave a marker
// to ensure that we don't reuse this slot; add new extra fields after it.
sb.append(SEP);
String activity = getActivity();
if (activity != null) {
sb.append(activity);
}
}
return sb.toString();
}
/** Returns the preferred theme, or null */
@Nullable
String computePreferredTheme() {
IProject project = mConfigChooser.getProject();
ManifestInfo manifest = ManifestInfo.get(project);
// Look up the screen size for the current state
ScreenSize screenSize = null;
Device device = getDevice();
if (device != null) {
List<State> states = device.getAllStates();
for (State state : states) {
FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
if (folderConfig != null) {
ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
screenSize = qualifier.getValue();
break;
}
}
}
// Look up the default/fallback theme to use for this project (which
// depends on the screen size when no particular theme is specified
// in the manifest)
String defaultTheme = manifest.getDefaultTheme(getTarget(), screenSize);
String preferred = defaultTheme;
if (getTheme() == null) {
// If we are rendering a layout in included context, pick the theme
// from the outer layout instead
String activity = getActivity();
if (activity != null) {
ActivityAttributes attributes = manifest.getActivityAttributes(activity);
if (attributes != null) {
preferred = attributes.getTheme();
}
}
if (preferred == null) {
preferred = defaultTheme;
}
setTheme(preferred);
}
return preferred;
}
private void checkThemePrefix() {
if (mTheme != null && !mTheme.startsWith(PREFIX_RESOURCE_REF)) {
if (mTheme.isEmpty()) {
computePreferredTheme();
return;
}
ResourceRepository frameworkRes = mConfigChooser.getClient().getFrameworkResources();
if (frameworkRes != null
&& frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + mTheme)) {
mTheme = ANDROID_STYLE_RESOURCE_PREFIX + mTheme;
} else {
mTheme = STYLE_RESOURCE_PREFIX + mTheme;
}
}
}
/**
* Initializes a string previously created with
* {@link #toPersistentString()}
*
* @param data the string to initialize back from
* @return true if the configuration was initialized
*/
boolean initialize(String data) {
String[] values = data.split(SEP);
if (values.length >= 6 && values.length <= 8) {
for (Device d : mConfigChooser.getDevices()) {
if (d.getName().equals(values[0])) {
mDevice = d;
String stateName = null;
FolderConfiguration config = null;
if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$
stateName = values[1];
config = DeviceConfigHelper.getFolderConfig(mDevice, stateName);
} else if (mDevice.getAllStates().size() > 0) {
State first = mDevice.getAllStates().get(0);
stateName = first.getName();
config = DeviceConfigHelper.getFolderConfig(first);
}
mState = getState(mDevice, stateName);
if (config != null) {
// Load locale. Note that this can get overwritten by the
// project-wide settings read below.
LocaleQualifier locale = Locale.ANY_QUALIFIER;
String locales[] = values[2].split(SEP_LOCALE);
if (locales.length >= 2 && locales[0].length() > 0
&& !LocaleQualifier.FAKE_VALUE.equals(locales[0])) {
String language = locales[0];
String region = locales[1];
if (region.length() > 0 && !LocaleQualifier.FAKE_VALUE.equals(region)) {
locale = LocaleQualifier.getQualifier(language + "-r" + region);
} else {
locale = new LocaleQualifier(language);
}
mLocale = Locale.create(locale);
}
// Decode the theme name: See {@link #getData}
mTheme = values[3];
if (mTheme.startsWith(MARKER_FRAMEWORK)) {
mTheme = ANDROID_STYLE_RESOURCE_PREFIX
+ mTheme.substring(MARKER_FRAMEWORK.length());
} else if (mTheme.startsWith(MARKER_PROJECT)) {
mTheme = STYLE_RESOURCE_PREFIX
+ mTheme.substring(MARKER_PROJECT.length());
} else {
checkThemePrefix();
}
mUiMode = UiMode.getEnum(values[4]);
if (mUiMode == null) {
mUiMode = UiMode.NORMAL;
}
mNightMode = NightMode.getEnum(values[5]);
if (mNightMode == null) {
mNightMode = NightMode.NOTNIGHT;
}
// element 7/values[6]: used to store render target in R9.
// No longer stored here. If adding more data, make
// sure you leave 7 alone.
Pair<Locale, IAndroidTarget> pair = loadRenderState(mConfigChooser);
if (pair != null) {
// We only use the "global" setting
if (!isLocaleSpecificLayout()) {
mLocale = pair.getFirst();
}
mTarget = pair.getSecond();
}
if (values.length == 8) {
mActivity = values[7];
}
return true;
}
}
}
}
return false;
}
/**
* Loads the render state (the locale and the render target, which are shared among
* all the layouts meaning that changing it in one will change it in all) and returns
* the current project-wide locale and render target to be used.
*
* @param chooser the {@link ConfigurationChooser} providing information about
* loaded targets
* @return a pair of a locale and a render target
*/
@Nullable
static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) {
IProject project = chooser.getProject();
if (project == null || !project.isAccessible()) {
return null;
}
try {
String data = project.getPersistentProperty(NAME_RENDER_STATE);
if (data != null) {
Locale locale = Locale.ANY;
IAndroidTarget target = null;
String[] values = data.split(SEP);
if (values.length == 2) {
LocaleQualifier qualifier = Locale.ANY_QUALIFIER;
String locales[] = values[0].split(SEP_LOCALE);
if (locales.length >= 2 && locales[0].length() > 0
&& !LocaleQualifier.FAKE_VALUE.equals(locales[0])) {
String language = locales[0];
String region = locales[1];
if (region.length() > 0 && !LocaleQualifier.FAKE_VALUE.equals(region)) {
locale = Locale.create(LocaleQualifier.getQualifier(language + "-r" + region));
} else {
locale = Locale.create(new LocaleQualifier(language));
}
} else {
locale = Locale.ANY;
}
if (AdtPrefs.getPrefs().isAutoPickRenderTarget()) {
target = ConfigurationMatcher.findDefaultRenderTarget(chooser);
} else {
String targetString = values[1];
target = stringToTarget(chooser, targetString);
// See if we should "correct" the rendering target to a
// better version. If you're using a pre-release version
// of the render target, and a final release is
// available and installed, we should switch to that
// one instead.
if (target != null) {
AndroidVersion version = target.getVersion();
List<IAndroidTarget> targetList = chooser.getTargetList();
if (version.getCodename() != null && targetList != null) {
int targetApiLevel = version.getApiLevel() + 1;
for (IAndroidTarget t : targetList) {
if (t.getVersion().getApiLevel() == targetApiLevel
&& t.isPlatform()) {
target = t;
break;
}
}
}
} else {
target = ConfigurationMatcher.findDefaultRenderTarget(chooser);
}
}
}
return Pair.of(locale, target);
}
return Pair.of(Locale.ANY, ConfigurationMatcher.findDefaultRenderTarget(chooser));
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
return null;
}
/**
* Saves the render state (the current locale and render target settings) into the
* project wide settings storage
*/
void saveRenderState() {
IProject project = mConfigChooser.getProject();
if (project == null) {
return;
}
try {
// Generate a persistent string from locale+target
StringBuilder sb = new StringBuilder(32);
Locale locale = getLocale();
if (locale != null) {
// locale[0]/[1] can be null sometimes when starting Eclipse
sb.append(locale.qualifier.getLanguage());
sb.append(SEP_LOCALE);
if (locale.qualifier.hasRegion()) {
sb.append(locale.qualifier.getRegion());
}
}
sb.append(SEP);
IAndroidTarget target = getTarget();
if (target != null) {
sb.append(targetToString(target));
sb.append(SEP);
}
project.setPersistentProperty(NAME_RENDER_STATE, sb.toString());
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
}
/**
* Returns a String id to represent an {@link IAndroidTarget} which can be translated
* back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id
* will never contain the {@link #SEP} character.
*
* @param target the target to return an id for
* @return an id for the given target; never null
*/
@NonNull
public static String targetToString(@NonNull IAndroidTarget target) {
return target.getFullName().replace(SEP, ""); //$NON-NLS-1$
}
/**
* Returns an {@link IAndroidTarget} that corresponds to the given id that was
* originally returned by {@link #targetToString}. May be null, if the platform is no
* longer available, or if the platform list has not yet been initialized.
*
* @param chooser the {@link ConfigurationChooser} providing information about
* loaded targets
* @param id the id that corresponds to the desired platform
* @return an {@link IAndroidTarget} that matches the given id, or null
*/
@Nullable
public static IAndroidTarget stringToTarget(
@NonNull ConfigurationChooser chooser,
@NonNull String id) {
List<IAndroidTarget> targetList = chooser.getTargetList();
if (targetList != null && targetList.size() > 0) {
for (IAndroidTarget target : targetList) {
if (id.equals(targetToString(target))) {
return target;
}
}
}
return null;
}
/**
* Returns an {@link IAndroidTarget} that corresponds to the given id that was
* originally returned by {@link #targetToString}. May be null, if the platform is no
* longer available, or if the platform list has not yet been initialized.
*
* @param id the id that corresponds to the desired platform
* @return an {@link IAndroidTarget} that matches the given id, or null
*/
@Nullable
public static IAndroidTarget stringToTarget(
@NonNull String id) {
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget[] targets = currentSdk.getTargets();
for (IAndroidTarget target : targets) {
if (id.equals(targetToString(target))) {
return target;
}
}
}
return null;
}
/**
* Returns the {@link State} by the given name for the given {@link Device}
*
* @param device the device
* @param name the name of the state
*/
@Nullable
static State getState(@Nullable Device device, @Nullable String name) {
if (device == null) {
return null;
} else if (name != null) {
State state = device.getState(name);
if (state != null) {
return state;
}
}
return device.getDefaultState();
}
/**
* Returns the currently selected {@link Density}. This is guaranteed to be non null.
*
* @return the density
*/
@NonNull
public Density getDensity() {
if (mFullConfig != null) {
DensityQualifier qual = mFullConfig.getDensityQualifier();
if (qual != null) {
// just a sanity check
Density d = qual.getValue();
if (d != Density.NODPI) {
return d;
}
}
}
// no config? return medium as the default density.
return Density.MEDIUM;
}
/**
* Get the next cyclical state after the given state
*
* @param from the state to start with
* @return the following state following
*/
@Nullable
public State getNextDeviceState(@Nullable State from) {
Device device = getDevice();
if (device == null) {
return null;
}
List<State> states = device.getAllStates();
for (int i = 0; i < states.size(); i++) {
if (states.get(i) == from) {
return states.get((i + 1) % states.size());
}
}
return null;
}
/**
* Returns true if this configuration supports the given rendering
* capability
*
* @param capability the capability to check
* @return true if the capability is supported
*/
public boolean supports(Capability capability) {
IAndroidTarget target = getTarget();
if (target != null) {
return RenderService.supports(target, capability);
}
return false;
}
@Override
public String toString() {
return Objects.toStringHelper(this.getClass())
.add("display", getDisplayName()) //$NON-NLS-1$
.add("persistent", toPersistentString()) //$NON-NLS-1$
.toString();
}
}