blob: 9724d40150b0ad0cdc68dde4487000430d250820 [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 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;
}
}
}