Fix the qualifier match algorithm.
Add proper support for density and keyboard state match.
Change-Id: I410aba52ee0f0d9df31fa2abdc9485054595263f
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java
index 7857997..8a12e19 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java
@@ -49,6 +49,13 @@
private final static int INDEX_COUNT = 14;
/**
+ * Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
+ */
+ public static int getQualifierCount() {
+ return INDEX_COUNT;
+ }
+
+ /**
* Sets the config from the qualifiers of a given <var>config</var>.
* @param config
*/
@@ -147,6 +154,16 @@
}
}
+ /**
+ * Returns a qualifier by its index. The total number of qualifiers can be accessed by
+ * {@link #getQualifierCount()}.
+ * @param index the index of the qualifier to return.
+ * @return the qualifier or null if there are none at the index.
+ */
+ public ResourceQualifier getQualifier(int index) {
+ return mQualifiers[index];
+ }
+
public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
}
@@ -446,7 +463,7 @@
}
/**
- * Returns whether the configuration match the given reference config.
+ * Returns whether the configuration is a match for the given reference config.
* <p/>A match means that:
* <ul>
* <li>This config does not use any qualifier not used by the reference config</li>
@@ -454,29 +471,24 @@
* the reference config.</li>
* </ul>
* @param referenceConfig The reference configuration to test against.
- * @return the number of matching qualifiers or -1 if the configurations are not compatible.
+ * @return true if the configuration matches.
*/
- public int match(FolderConfiguration referenceConfig) {
- int matchCount = 0;
-
+ public boolean isMatchFor(FolderConfiguration referenceConfig) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
ResourceQualifier testQualifier = mQualifiers[i];
ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
- // we only care if testQualifier is non null. If it's null, it's a match but
- // without increasing the matchCount.
+ // we only care if testQualifier is non null.
if (testQualifier != null) {
- if (referenceQualifier == null) {
- return -1;
- } else if (testQualifier.equals(referenceQualifier) == false) {
- return -1;
+ if (referenceQualifier == null) { // reference config doesn't specify anything
+ // for this qualifier so we refuse it.
+ return false;
+ } else if (testQualifier.isMatchFor(referenceQualifier) == false) {
+ return false;
}
-
- // the qualifier match, increment the count
- matchCount++;
}
}
- return matchCount;
+ return true;
}
/**
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/KeyboardStateQualifier.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/KeyboardStateQualifier.java
index 6f87510..2777328 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/KeyboardStateQualifier.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/KeyboardStateQualifier.java
@@ -142,6 +142,42 @@
}
@Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof KeyboardStateQualifier) {
+ KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)qualifier;
+
+ // special case where EXPOSED can be used for SOFT
+ if (referenceQualifier.mValue == KeyboardState.SOFT &&
+ mValue == KeyboardState.EXPOSED) {
+ return true;
+ }
+
+ return referenceQualifier.mValue == mValue;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ KeyboardStateQualifier compareQualifier = (KeyboardStateQualifier)compareTo;
+ KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)reference;
+ if (referenceQualifier.mValue == KeyboardState.SOFT) { // only case where there could be a
+ // better qualifier
+ // only return true if it's a better value.
+ if (compareQualifier.mValue == KeyboardState.EXPOSED && mValue == KeyboardState.SOFT) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
public boolean equals(Object qualifier) {
if (qualifier instanceof KeyboardStateQualifier) {
return mValue == ((KeyboardStateQualifier)qualifier).mValue;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/PixelDensityQualifier.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/PixelDensityQualifier.java
index f75e9cb..d67d4ae 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/PixelDensityQualifier.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/PixelDensityQualifier.java
@@ -184,6 +184,38 @@
}
@Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof PixelDensityQualifier) {
+ // as long as there's a density qualifier, it's always a match.
+ // The best match will be found later.
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ PixelDensityQualifier compareQ = (PixelDensityQualifier)compareTo;
+ PixelDensityQualifier referenceQ = (PixelDensityQualifier)reference;
+
+ if (mValue == referenceQ.mValue && compareQ.mValue != referenceQ.mValue) {
+ // got exact value, this is the best!
+ return true;
+ } else {
+ // in all case we're going to prefer the higher dpi.
+ // if reference is high, we want highest dpi.
+ // if reference is medium, we'll prefer to scale down high dpi, than scale up low dpi
+ // if reference if low, we'll prefer to scale down high than medium (2:1 over 4:3)
+ return mValue.mDpiValue > compareQ.mValue.mDpiValue;
+ }
+ }
+
+ @Override
public boolean equals(Object qualifier) {
if (qualifier instanceof PixelDensityQualifier) {
return mValue == ((PixelDensityQualifier)qualifier).mValue;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ResourceQualifier.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ResourceQualifier.java
index bfee8d2..ba54ad0 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ResourceQualifier.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ResourceQualifier.java
@@ -62,6 +62,32 @@
*/
public abstract String getFolderSegment(IAndroidTarget target);
+ /**
+ * Returns whether the given qualifier is a match for the receiver.
+ * <p/>The default implementation returns the result of {@link #equals(Object)}.
+ * <p/>Children class that re-implements this must implement
+ * {@link #isBetterMatchThan(ResourceQualifier, ResourceQualifier)} too.
+ * @param qualifier the reference qualifier
+ * @return true if the receiver is a match.
+ */
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ return equals(qualifier);
+ }
+
+ /**
+ * Returns true if the receiver is a better match for the given <var>reference</var> than
+ * the given <var>compareTo</var> comparable.
+ * @param compareTo The {@link ResourceQualifier} to compare to. Can be null, in which
+ * case the method must return <code>true</code>.
+ * @param reference The reference qualifier value for which the match is.
+ * @return true if the receiver is a better match.
+ */
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ // the default is to always return false. This gives less overhead than always returning
+ // true, as it would only compare same values anyway.
+ return false;
+ }
+
@Override
public String toString() {
return getFolderSegment(null);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
index e8c3687..9d715d0 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
@@ -22,6 +22,7 @@
import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
import com.android.ide.eclipse.adt.internal.resources.manager.files.IAbstractFolder;
import com.android.layoutlib.api.IResourceValue;
import com.android.layoutlib.utils.ResourceValue;
@@ -505,79 +506,102 @@
* Returns the best matching {@link Resource}.
* @param resources the list of {@link Resource} to choose from.
* @param referenceConfig the {@link FolderConfiguration} to match.
+ * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
*/
private Resource findMatchingConfiguredResource(List<? extends Resource> resources,
FolderConfiguration referenceConfig) {
- // look for resources with the maximum number of qualifier match.
- int currentMax = -1;
+ //
+ // 1: eliminate resources that contradict the reference configuration
+ // 2: pick next qualifier type
+ // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
+ // 4: eliminate resources that don't use this qualifier.
+ // 5: if more than one resource left, go back to 2.
+ //
+ // The precedence of the qualifiers is more important than the number of qualifiers that
+ // exactly match the device.
+
+ // 1: eliminate resources that contradict
ArrayList<Resource> matchingResources = new ArrayList<Resource>();
for (int i = 0 ; i < resources.size(); i++) {
Resource res = resources.get(i);
- int count = res.getConfiguration().match(referenceConfig);
- if (count > currentMax) {
- matchingResources.clear();
- matchingResources.add(res);
- currentMax = count;
- } else if (count != -1 && count == currentMax) {
+ if (res.getConfiguration().isMatchFor(referenceConfig)) {
matchingResources.add(res);
}
}
- // if we have more than one match, we look for the match with the qualifiers with the
- // highest priority.
- Resource resMatch = null;
+ // if there is only one match, just take it
if (matchingResources.size() == 1) {
- resMatch = matchingResources.get(0);
- } else if (matchingResources.size() > 1) {
- // More than one resource with the same number of qualifier match.
- // We loop, looking for the resource with the highest priority qualifiers.
- ArrayList<Resource> tmpResources = new ArrayList<Resource>();
- int startIndex = 0;
- while (matchingResources.size() > 1) {
- int highest = -1;
- for (int i = 0 ; i < matchingResources.size() ; i++) {
- Resource folder = matchingResources.get(i);
+ return matchingResources.get(0);
+ } else if (matchingResources.size() == 0) {
+ return null;
+ }
- // get highest priority qualifiers.
- int m = folder.getConfiguration().getHighestPriorityQualifier(startIndex);
+ // 2. Loop on the qualifiers, and eliminate matches
+ final int count = FolderConfiguration.getQualifierCount();
+ for (int q = 0 ; q < count ; q++) {
+ // look to see if one resource has this qualifier.
+ // At the same time also record the best match value for the qualifier (if applicable).
+ ResourceQualifier referenceQualifier = referenceConfig.getQualifier(q);
- // add to the list if highest.
- if (m != -1) {
- if (highest == -1 || m == highest) {
- tmpResources.add(folder);
- highest = m;
- } else if (m < highest) { // highest priority == lowest index.
- tmpResources.clear();
- tmpResources.add(folder);
+ if (referenceQualifier != null) { // no need to check if it's null, since the loop
+ // above will have removed the resources anyway.
+ boolean found = false;
+ ResourceQualifier bestMatch = null;
+ for (Resource res : matchingResources) {
+ ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
+ if (qualifier != null) {
+ // set the flag.
+ found = true;
+
+ // now check for a best match.
+ if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
+ bestMatch = qualifier;
}
}
}
- // at this point, we have a list with 1+ resources that all have the same highest
- // priority qualifiers. Go through the list again looking for the next highest
- // priority qualifier.
- startIndex = highest + 1;
+ // if a resources as a qualifier at the current index, remove all the resources that
+ // do not have one.
+ // If there is one, and we have a bestComparable, also check that it's equal to the
+ // best comparable.
+ if (found) {
+ for (int i = 0 ; i < matchingResources.size(); ) {
+ Resource res = matchingResources.get(i);
+ ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
- // this should not happen, but it's better to check.
- if (matchingResources.size() == tmpResources.size() && highest == -1) {
- // this means all the resources match with the same qualifiers
- // (highest == -1 means we reached the end of the qualifier list)
- // In this case, we arbitrarily take the first resource.
- matchingResources.clear();
- matchingResources.add(tmpResources.get(0));
- } else {
- matchingResources.clear();
- matchingResources.addAll(tmpResources);
+ if (qualifier == null) { // no qualifier? remove the resources
+ matchingResources.remove(res);
+ } else if (bestMatch != null && bestMatch.equals(qualifier) == false) {
+ // if there is a best match, only accept the resource if the qualifier
+ // has the same best value.
+ matchingResources.remove(res);
+ } else {
+ i++;
+ }
+ }
+
+ // at this point we may have run out of matching resources before going
+ // through all the qualifiers.
+ if (matchingResources.size() == 1) {
+ return matchingResources.get(0);
+ } else if (matchingResources.size() == 0) {
+ return null;
+ }
}
- tmpResources.clear();
}
-
- // we should have only one match here.
- resMatch = matchingResources.get(0);
}
- return resMatch;
+ // went through all the qualifiers. We should not have more than one
+ switch (matchingResources.size()) {
+ case 0:
+ return null;
+ case 1:
+ return matchingResources.get(1);
+ case 2:
+ assert false;
+ }
+ return null;
}
/**