From cf51575c5ec4afabe2c85d7dbd33be5ebcb17fcb Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet Date: Tue, 22 Sep 2009 15:04:35 -0700 Subject: [PATCH] Fix the qualifier match algorithm. Add proper support for density and keyboard state match. Change-Id: I410aba52ee0f0d9df31fa2abdc9485054595263f --- .../configurations/FolderConfiguration.java | 42 +++--- .../KeyboardStateQualifier.java | 36 +++++ .../configurations/PixelDensityQualifier.java | 32 +++++ .../configurations/ResourceQualifier.java | 26 ++++ .../resources/manager/ProjectResources.java | 124 +++++++++++------- 5 files changed, 195 insertions(+), 65 deletions(-) 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 7857997ce..8a12e1944 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 @@ -48,6 +48,13 @@ public final class FolderConfiguration implements Comparableconfig. * @param config @@ -147,6 +154,16 @@ public final class FolderConfiguration implements ComparableA match means that: *
    *
  • This config does not use any qualifier not used by the reference config
  • @@ -454,29 +471,24 @@ public final class FolderConfiguration implements Comparable *
* @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 6f87510bd..2777328ed 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 @@ -141,6 +141,42 @@ public final class KeyboardStateQualifier extends ResourceQualifier { return false; } + @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) { 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 f75e9cbef..d67d4ae0b 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 @@ -183,6 +183,38 @@ public final class PixelDensityQualifier extends ResourceQualifier { return false; } + @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) { 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 bfee8d231..ba54ad083 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 class ResourceQualifier implements Comparable */ public abstract String getFolderSegment(IAndroidTarget target); + /** + * Returns whether the given qualifier is a match for the receiver. + *

The default implementation returns the result of {@link #equals(Object)}. + *

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 reference than + * the given compareTo comparable. + * @param compareTo The {@link ResourceQualifier} to compare to. Can be null, in which + * case the method must return true. + * @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 e8c368732..9d715d02f 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.ResourceType; 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 @@ public class ProjectResources implements IResourceRepository { * 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 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 matchingResources = new ArrayList(); 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 tmpResources = new ArrayList(); - 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; } /**