diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java | 843 |
1 files changed, 843 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java new file mode 100644 index 000000000..9724d4015 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java @@ -0,0 +1,843 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.editors.layout.configuration; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.resources.ResourceFile; +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.LocaleQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +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.AdtUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; +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.Sdk; +import com.android.ide.eclipse.adt.io.IFileWrapper; +import com.android.resources.Density; +import com.android.resources.NightMode; +import com.android.resources.ResourceType; +import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenSize; +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.sdklib.repository.PkgProps; +import com.android.utils.Pair; +import com.android.utils.SparseIntArray; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.ui.IEditorPart; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Produces matches for configurations + * <p> + * See algorithm described here: + * http://developer.android.com/guide/topics/resources/providing-resources.html + */ +public class ConfigurationMatcher { + private static final boolean PREFER_RECENT_RENDER_TARGETS = true; + + private final ConfigurationChooser mConfigChooser; + private final Configuration mConfiguration; + private final IFile mEditedFile; + private final ProjectResources mResources; + private final boolean mUpdateUi; + + ConfigurationMatcher(ConfigurationChooser chooser) { + this(chooser, chooser.getConfiguration(), chooser.getEditedFile(), + chooser.getResources(), true); + } + + ConfigurationMatcher( + @NonNull ConfigurationChooser chooser, + @NonNull Configuration configuration, + @Nullable IFile editedFile, + @Nullable ProjectResources resources, + boolean updateUi) { + mConfigChooser = chooser; + mConfiguration = configuration; + mEditedFile = editedFile; + mResources = resources; + mUpdateUi = updateUi; + } + + // ---- Finding matching configurations ---- + + private static class ConfigBundle { + private final FolderConfiguration config; + private int localeIndex; + private int dockModeIndex; + private int nightModeIndex; + + private ConfigBundle() { + config = new FolderConfiguration(); + } + + private ConfigBundle(ConfigBundle bundle) { + config = new FolderConfiguration(); + config.set(bundle.config); + localeIndex = bundle.localeIndex; + dockModeIndex = bundle.dockModeIndex; + nightModeIndex = bundle.nightModeIndex; + } + } + + private static class ConfigMatch { + final FolderConfiguration testConfig; + final Device device; + final State state; + final ConfigBundle bundle; + + public ConfigMatch(@NonNull FolderConfiguration testConfig, @NonNull Device device, + @NonNull State state, @NonNull ConfigBundle bundle) { + this.testConfig = testConfig; + this.device = device; + this.state = state; + this.bundle = bundle; + } + + @Override + public String toString() { + return device.getName() + " - " + state.getName(); + } + } + + /** + * Checks whether the current edited file is the best match for a given config. + * <p> + * This tests against other versions of the same layout in the project. + * <p> + * The given config must be compatible with the current edited file. + * @param config the config to test. + * @return true if the current edited file is the best match in the project for the + * given config. + */ + public boolean isCurrentFileBestMatchFor(FolderConfiguration config) { + ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(), + ResourceType.LAYOUT, config); + + if (match != null) { + return match.getFile().equals(mEditedFile); + } else { + // if we stop here that means the current file is not even a match! + AdtPlugin.log(IStatus.ERROR, "Current file is not a match for the given config."); + } + + return false; + } + + /** + * Adapts the current device/config selection so that it's compatible with + * the configuration. + * <p> + * If the current selection is compatible, nothing is changed. + * <p> + * If it's not compatible, configs from the current devices are tested. + * <p> + * If none are compatible, it reverts to + * {@link #findAndSetCompatibleConfig(boolean)} + */ + void adaptConfigSelection(boolean needBestMatch) { + // check the device config (ie sans locale) + boolean needConfigChange = true; // if still true, we need to find another config. + boolean currentConfigIsCompatible = false; + State selectedState = mConfiguration.getDeviceState(); + FolderConfiguration editedConfig = mConfiguration.getEditedConfig(); + if (selectedState != null) { + FolderConfiguration currentConfig = DeviceConfigHelper.getFolderConfig(selectedState); + if (currentConfig != null && editedConfig.isMatchFor(currentConfig)) { + currentConfigIsCompatible = true; // current config is compatible + if (!needBestMatch || isCurrentFileBestMatchFor(currentConfig)) { + needConfigChange = false; + } + } + } + + if (needConfigChange) { + List<Locale> localeList = mConfigChooser.getLocaleList(); + + // if the current state/locale isn't a correct match, then + // look for another state/locale in the same device. + FolderConfiguration testConfig = new FolderConfiguration(); + + // first look in the current device. + State matchState = null; + int localeIndex = -1; + Device device = mConfiguration.getDevice(); + if (device != null) { + mainloop: for (State state : device.getAllStates()) { + testConfig.set(DeviceConfigHelper.getFolderConfig(state)); + + // loop on the locales. + for (int i = 0 ; i < localeList.size() ; i++) { + Locale locale = localeList.get(i); + + // update the test config with the locale qualifiers + testConfig.setLocaleQualifier(locale.qualifier); + + + if (editedConfig.isMatchFor(testConfig) && + isCurrentFileBestMatchFor(testConfig)) { + matchState = state; + localeIndex = i; + break mainloop; + } + } + } + } + + if (matchState != null) { + mConfiguration.setDeviceState(matchState, true); + Locale locale = localeList.get(localeIndex); + mConfiguration.setLocale(locale, true); + if (mUpdateUi) { + mConfigChooser.selectDeviceState(matchState); + mConfigChooser.selectLocale(locale); + } + mConfiguration.syncFolderConfig(); + } else { + // no match in current device with any state/locale + // attempt to find another device that can display this + // particular state. + findAndSetCompatibleConfig(currentConfigIsCompatible); + } + } + } + + /** + * Finds a device/config that can display a configuration. + * <p> + * Once found the device and config combos are set to the config. + * <p> + * If there is no compatible configuration, a custom one is created. + * + * @param favorCurrentConfig if true, and no best match is found, don't + * change the current config. This must only be true if the + * current config is compatible. + */ + void findAndSetCompatibleConfig(boolean favorCurrentConfig) { + List<Locale> localeList = mConfigChooser.getLocaleList(); + Collection<Device> devices = mConfigChooser.getDevices(); + FolderConfiguration editedConfig = mConfiguration.getEditedConfig(); + FolderConfiguration currentConfig = mConfiguration.getFullConfig(); + + // list of compatible device/state/locale + List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>(); + + // list of actual best match (ie the file is a best match for the + // device/state) + List<ConfigMatch> bestMatches = new ArrayList<ConfigMatch>(); + + // get a locale that match the host locale roughly (may not be exact match on the region.) + int localeHostMatch = getLocaleMatch(); + + // build a list of combinations of non standard qualifiers to add to each device's + // qualifier set when testing for a match. + // These qualifiers are: locale, night-mode, car dock. + List<ConfigBundle> configBundles = new ArrayList<ConfigBundle>(200); + + // If the edited file has locales, then we have to select a matching locale from + // the list. + // However, if it doesn't, we don't randomly take the first locale, we take one + // matching the current host locale (making sure it actually exist in the project) + int start, max; + if (editedConfig.getLocaleQualifier() != null || localeHostMatch == -1) { + // add all the locales + start = 0; + max = localeList.size(); + } else { + // only add the locale host match + start = localeHostMatch; + max = localeHostMatch + 1; // test is < + } + + for (int i = start ; i < max ; i++) { + Locale l = localeList.get(i); + + ConfigBundle bundle = new ConfigBundle(); + bundle.config.setLocaleQualifier(l.qualifier); + + bundle.localeIndex = i; + configBundles.add(bundle); + } + + // add the dock mode to the bundle combinations. + addDockModeToBundles(configBundles); + + // add the night mode to the bundle combinations. + addNightModeToBundles(configBundles); + + addRenderTargetToBundles(configBundles); + + for (Device device : devices) { + for (State state : device.getAllStates()) { + + // loop on the list of config bundles to create full + // configurations. + FolderConfiguration stateConfig = DeviceConfigHelper.getFolderConfig(state); + for (ConfigBundle bundle : configBundles) { + // create a new config with device config + FolderConfiguration testConfig = new FolderConfiguration(); + testConfig.set(stateConfig); + + // add on top of it, the extra qualifiers from the bundle + testConfig.add(bundle.config); + + if (editedConfig.isMatchFor(testConfig)) { + // this is a basic match. record it in case we don't + // find a match + // where the edited file is a best config. + anyMatches.add(new ConfigMatch(testConfig, device, state, bundle)); + + if (isCurrentFileBestMatchFor(testConfig)) { + // this is what we want. + bestMatches.add(new ConfigMatch(testConfig, device, state, bundle)); + } + } + } + } + } + + if (bestMatches.size() == 0) { + if (favorCurrentConfig) { + // quick check + if (!editedConfig.isMatchFor(currentConfig)) { + AdtPlugin.log(IStatus.ERROR, + "favorCurrentConfig can only be true if the current config is compatible"); + } + + // just display the warning + AdtPlugin.printErrorToConsole(mEditedFile.getProject(), + String.format( + "'%1$s' is not a best match for any device/locale combination.", + editedConfig.toDisplayString()), + String.format( + "Displaying it with '%1$s'", + currentConfig.toDisplayString())); + } else if (anyMatches.size() > 0) { + // select the best device anyway. + ConfigMatch match = selectConfigMatch(anyMatches); + mConfiguration.setDevice(match.device, true); + mConfiguration.setDeviceState(match.state, true); + mConfiguration.setLocale(localeList.get(match.bundle.localeIndex), true); + mConfiguration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true); + mConfiguration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), + true); + + if (mUpdateUi) { + mConfigChooser.selectDevice(mConfiguration.getDevice()); + mConfigChooser.selectDeviceState(mConfiguration.getDeviceState()); + mConfigChooser.selectLocale(mConfiguration.getLocale()); + } + + mConfiguration.syncFolderConfig(); + + // TODO: display a better warning! + AdtPlugin.printErrorToConsole(mEditedFile.getProject(), + String.format( + "'%1$s' is not a best match for any device/locale combination.", + editedConfig.toDisplayString()), + String.format( + "Displaying it with '%1$s' which is compatible, but will " + + "actually be displayed with another more specific version of " + + "the layout.", + currentConfig.toDisplayString())); + + } else { + // TODO: there is no device/config able to display the layout, create one. + // For the base config values, we'll take the first device and state, + // and replace whatever qualifier required by the layout file. + } + } else { + ConfigMatch match = selectConfigMatch(bestMatches); + mConfiguration.setDevice(match.device, true); + mConfiguration.setDeviceState(match.state, true); + mConfiguration.setLocale(localeList.get(match.bundle.localeIndex), true); + mConfiguration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true); + mConfiguration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), true); + + mConfiguration.syncFolderConfig(); + + if (mUpdateUi) { + mConfigChooser.selectDevice(mConfiguration.getDevice()); + mConfigChooser.selectDeviceState(mConfiguration.getDeviceState()); + mConfigChooser.selectLocale(mConfiguration.getLocale()); + } + } + } + + private void addRenderTargetToBundles(List<ConfigBundle> configBundles) { + Pair<Locale, IAndroidTarget> state = Configuration.loadRenderState(mConfigChooser); + if (state != null) { + IAndroidTarget target = state.getSecond(); + if (target != null) { + int apiLevel = target.getVersion().getApiLevel(); + for (ConfigBundle bundle : configBundles) { + bundle.config.setVersionQualifier( + new VersionQualifier(apiLevel)); + } + } + } + } + + private void addDockModeToBundles(List<ConfigBundle> addConfig) { + ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>(); + + // loop on each item and for each, add all variations of the dock modes + for (ConfigBundle bundle : addConfig) { + int index = 0; + for (UiMode mode : UiMode.values()) { + ConfigBundle b = new ConfigBundle(bundle); + b.config.setUiModeQualifier(new UiModeQualifier(mode)); + b.dockModeIndex = index++; + list.add(b); + } + } + + addConfig.clear(); + addConfig.addAll(list); + } + + private void addNightModeToBundles(List<ConfigBundle> addConfig) { + ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>(); + + // loop on each item and for each, add all variations of the night modes + for (ConfigBundle bundle : addConfig) { + int index = 0; + for (NightMode mode : NightMode.values()) { + ConfigBundle b = new ConfigBundle(bundle); + b.config.setNightModeQualifier(new NightModeQualifier(mode)); + b.nightModeIndex = index++; + list.add(b); + } + } + + addConfig.clear(); + addConfig.addAll(list); + } + + private int getLocaleMatch() { + java.util.Locale defaultLocale = java.util.Locale.getDefault(); + if (defaultLocale != null) { + String currentLanguage = defaultLocale.getLanguage(); + String currentRegion = defaultLocale.getCountry(); + + List<Locale> localeList = mConfigChooser.getLocaleList(); + final int count = localeList.size(); + for (int l = 0; l < count; l++) { + Locale locale = localeList.get(l); + LocaleQualifier qualifier = locale.qualifier; + + // there's always a ##/Other or ##/Any (which is the same, the region + // contains FAKE_REGION_VALUE). If we don't find a perfect region match + // we take the fake region. Since it's last in the list, this makes the + // test easy. + if (qualifier.getLanguage().equals(currentLanguage) && + (qualifier.getRegion() == null || qualifier.getRegion().equals(currentRegion))) { + return l; + } + } + + // if no locale match the current local locale, it's likely that it is + // the default one which is the last one. + return count - 1; + } + + return -1; + } + + private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) { + // API 11-13: look for a x-large device + Comparator<ConfigMatch> comparator = null; + Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + IAndroidTarget projectTarget = sdk.getTarget(mEditedFile.getProject()); + if (projectTarget != null) { + int apiLevel = projectTarget.getVersion().getApiLevel(); + if (apiLevel >= 11 && apiLevel < 14) { + // TODO: Maybe check the compatible-screen tag in the manifest to figure out + // what kind of device should be used for display. + comparator = new TabletConfigComparator(); + } + } + } + if (comparator == null) { + // lets look for a high density device + comparator = new PhoneConfigComparator(); + } + Collections.sort(matches, comparator); + + // Look at the currently active editor to see if it's a layout editor, and if so, + // look up its configuration and if the configuration is in our match list, + // use it. This means we "preserve" the current configuration when you open + // new layouts. + IEditorPart activeEditor = AdtUtils.getActiveEditor(); + LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor); + if (delegate != null + // (Only do this when the two files are in the same project) + && delegate.getEditor().getProject() == mEditedFile.getProject()) { + FolderConfiguration configuration = delegate.getGraphicalEditor().getConfiguration(); + if (configuration != null) { + for (ConfigMatch match : matches) { + if (configuration.equals(match.testConfig)) { + return match; + } + } + } + } + + // the list has been sorted so that the first item is the best config + return matches.get(0); + } + + /** Return the default render target to use, or null if no strong preference */ + @Nullable + static IAndroidTarget findDefaultRenderTarget(ConfigurationChooser chooser) { + if (PREFER_RECENT_RENDER_TARGETS) { + // Use the most recent target + List<IAndroidTarget> targetList = chooser.getTargetList(); + if (!targetList.isEmpty()) { + return targetList.get(targetList.size() - 1); + } + } + + IProject project = chooser.getProject(); + // Default to layoutlib version 5 + Sdk current = Sdk.getCurrent(); + if (current != null) { + IAndroidTarget projectTarget = current.getTarget(project); + int minProjectApi = Integer.MAX_VALUE; + if (projectTarget != null) { + if (!projectTarget.isPlatform() && projectTarget.hasRenderingLibrary()) { + // Renderable non-platform targets are all going to be adequate (they + // will have at least version 5 of layoutlib) so use the project + // target as the render target. + return projectTarget; + } + + if (projectTarget.getVersion().isPreview() + && projectTarget.hasRenderingLibrary()) { + // If the project target is a preview version, then just use it + return projectTarget; + } + + minProjectApi = projectTarget.getVersion().getApiLevel(); + } + + // We want to pick a render target that contains at least version 5 (and + // preferably version 6) of the layout library. To do this, we go through the + // targets and pick the -smallest- API level that is both simultaneously at + // least as big as the project API level, and supports layoutlib level 5+. + IAndroidTarget best = null; + int bestApiLevel = Integer.MAX_VALUE; + + for (IAndroidTarget target : current.getTargets()) { + // Non-platform targets are not chosen as the default render target + if (!target.isPlatform()) { + continue; + } + + int apiLevel = target.getVersion().getApiLevel(); + + // Ignore targets that have a lower API level than the minimum project + // API level: + if (apiLevel < minProjectApi) { + continue; + } + + // Look up the layout lib API level. This property is new so it will only + // be defined for version 6 or higher, which means non-null is adequate + // to see if this target is eligible: + String property = target.getProperty(PkgProps.LAYOUTLIB_API); + // In addition, Android 3.0 with API level 11 had version 5.0 which is adequate: + if (property != null || apiLevel >= 11) { + if (apiLevel < bestApiLevel) { + bestApiLevel = apiLevel; + best = target; + } + } + } + + return best; + } + + return null; + } + + /** + * Attempts to find a close state among a list + * + * @param oldConfig the reference config. + * @param states the list of states to search through + * @return the name of the closest state match, or possibly null if no states are compatible + * (this can only happen if the states don't have a single qualifier that is the same). + */ + @Nullable + static String getClosestMatch(@NonNull FolderConfiguration oldConfig, + @NonNull List<State> states) { + + // create 2 lists as we're going to go through one and put the + // candidates in the other. + List<State> list1 = new ArrayList<State>(states.size()); + List<State> list2 = new ArrayList<State>(states.size()); + + list1.addAll(states); + + final int count = FolderConfiguration.getQualifierCount(); + for (int i = 0 ; i < count ; i++) { + // compute the new candidate list by only taking states that have + // the same i-th qualifier as the old state + for (State s : list1) { + ResourceQualifier oldQualifier = oldConfig.getQualifier(i); + + FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s); + ResourceQualifier newQualifier = + folderConfig != null ? folderConfig.getQualifier(i) : null; + + if (oldQualifier == null) { + if (newQualifier == null) { + list2.add(s); + } + } else if (oldQualifier.equals(newQualifier)) { + list2.add(s); + } + } + + // at any moment if the new candidate list contains only one match, its name + // is returned. + if (list2.size() == 1) { + return list2.get(0).getName(); + } + + // if the list is empty, then all the new states failed. It is considered ok, and + // we move to the next qualifier anyway. This way, if a qualifier is different for + // all new states it is simply ignored. + if (list2.size() != 0) { + // move the candidates back into list1. + list1.clear(); + list1.addAll(list2); + list2.clear(); + } + } + + // the only way to reach this point is if there's an exact match. + // (if there are more than one, then there's a duplicate state and it doesn't matter, + // we take the first one). + if (list1.size() > 0) { + return list1.get(0).getName(); + } + + return null; + } + + /** + * Returns the layout {@link IFile} which best matches the configuration + * selected in the given configuration chooser. + * + * @param chooser the associated configuration chooser holding project state + * @return the file which best matches the settings + */ + @Nullable + public static IFile getBestFileMatch(ConfigurationChooser chooser) { + // get the resources of the file's project. + ResourceManager manager = ResourceManager.getInstance(); + ProjectResources resources = manager.getProjectResources(chooser.getProject()); + if (resources == null) { + return null; + } + + // From the resources, look for a matching file + IFile editedFile = chooser.getEditedFile(); + if (editedFile == null) { + return null; + } + String name = editedFile.getName(); + FolderConfiguration config = chooser.getConfiguration().getFullConfig(); + ResourceFile match = resources.getMatchingFile(name, ResourceType.LAYOUT, config); + + if (match != null) { + // In Eclipse, the match's file is always an instance of IFileWrapper + return ((IFileWrapper) match.getFile()).getIFile(); + } + + return null; + } + + /** + * Note: this comparator imposes orderings that are inconsistent with equals. + */ + private static class TabletConfigComparator implements Comparator<ConfigMatch> { + @Override + public int compare(ConfigMatch o1, ConfigMatch o2) { + FolderConfiguration config1 = o1 != null ? o1.testConfig : null; + FolderConfiguration config2 = o2 != null ? o2.testConfig : null; + if (config1 == null) { + if (config2 == null) { + return 0; + } else { + return -1; + } + } else if (config2 == null) { + return 1; + } + + ScreenSizeQualifier size1 = config1.getScreenSizeQualifier(); + ScreenSizeQualifier size2 = config2.getScreenSizeQualifier(); + ScreenSize ss1 = size1 != null ? size1.getValue() : ScreenSize.NORMAL; + ScreenSize ss2 = size2 != null ? size2.getValue() : ScreenSize.NORMAL; + + // X-LARGE is better than all others (which are considered identical) + // if both X-LARGE, then LANDSCAPE is better than all others (which are identical) + + if (ss1 == ScreenSize.XLARGE) { + if (ss2 == ScreenSize.XLARGE) { + ScreenOrientationQualifier orientation1 = + config1.getScreenOrientationQualifier(); + ScreenOrientation so1 = orientation1.getValue(); + if (so1 == null) { + so1 = ScreenOrientation.PORTRAIT; + } + ScreenOrientationQualifier orientation2 = + config2.getScreenOrientationQualifier(); + ScreenOrientation so2 = orientation2.getValue(); + if (so2 == null) { + so2 = ScreenOrientation.PORTRAIT; + } + + if (so1 == ScreenOrientation.LANDSCAPE) { + if (so2 == ScreenOrientation.LANDSCAPE) { + return 0; + } else { + return -1; + } + } else if (so2 == ScreenOrientation.LANDSCAPE) { + return 1; + } else { + return 0; + } + } else { + return -1; + } + } else if (ss2 == ScreenSize.XLARGE) { + return 1; + } else { + return 0; + } + } + } + + /** + * Note: this comparator imposes orderings that are inconsistent with equals. + */ + private static class PhoneConfigComparator implements Comparator<ConfigMatch> { + + private final SparseIntArray mDensitySort = new SparseIntArray(4); + + public PhoneConfigComparator() { + // put the sort order for the density. + mDensitySort.put(Density.HIGH.getDpiValue(), 1); + mDensitySort.put(Density.MEDIUM.getDpiValue(), 2); + mDensitySort.put(Density.XHIGH.getDpiValue(), 3); + mDensitySort.put(Density.LOW.getDpiValue(), 4); + } + + @Override + public int compare(ConfigMatch o1, ConfigMatch o2) { + FolderConfiguration config1 = o1 != null ? o1.testConfig : null; + FolderConfiguration config2 = o2 != null ? o2.testConfig : null; + if (config1 == null) { + if (config2 == null) { + return 0; + } else { + return -1; + } + } else if (config2 == null) { + return 1; + } + + int dpi1 = Density.DEFAULT_DENSITY; + int dpi2 = Density.DEFAULT_DENSITY; + + DensityQualifier dpiQualifier1 = config1.getDensityQualifier(); + if (dpiQualifier1 != null) { + Density value = dpiQualifier1.getValue(); + dpi1 = value != null ? value.getDpiValue() : Density.DEFAULT_DENSITY; + } + dpi1 = mDensitySort.get(dpi1, 100 /* valueIfKeyNotFound*/); + + DensityQualifier dpiQualifier2 = config2.getDensityQualifier(); + if (dpiQualifier2 != null) { + Density value = dpiQualifier2.getValue(); + dpi2 = value != null ? value.getDpiValue() : Density.DEFAULT_DENSITY; + } + dpi2 = mDensitySort.get(dpi2, 100 /* valueIfKeyNotFound*/); + + if (dpi1 == dpi2) { + // portrait is better + ScreenOrientation so1 = ScreenOrientation.PORTRAIT; + ScreenOrientationQualifier orientationQualifier1 = + config1.getScreenOrientationQualifier(); + if (orientationQualifier1 != null) { + so1 = orientationQualifier1.getValue(); + if (so1 == null) { + so1 = ScreenOrientation.PORTRAIT; + } + } + ScreenOrientation so2 = ScreenOrientation.PORTRAIT; + ScreenOrientationQualifier orientationQualifier2 = + config2.getScreenOrientationQualifier(); + if (orientationQualifier2 != null) { + so2 = orientationQualifier2.getValue(); + if (so2 == null) { + so2 = ScreenOrientation.PORTRAIT; + } + } + + if (so1 == ScreenOrientation.PORTRAIT) { + if (so2 == ScreenOrientation.PORTRAIT) { + return 0; + } else { + return -1; + } + } else if (so2 == ScreenOrientation.PORTRAIT) { + return 1; + } else { + return 0; + } + } + + return dpi1 - dpi2; + } + } +} |