diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java | 1091 |
1 files changed, 1091 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java new file mode 100644 index 000000000..c4253cddf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java @@ -0,0 +1,1091 @@ +/* + * 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(); + } +} |