Fix the qualifier match algorithm.
Add proper support for density and keyboard state match. Change-Id: I410aba52ee0f0d9df31fa2abdc9485054595263f
This commit is contained in:
@@ -48,6 +48,13 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
|
|||||||
private final static int INDEX_VERSION = 13;
|
private final static int INDEX_VERSION = 13;
|
||||||
private final static int INDEX_COUNT = 14;
|
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>.
|
* Sets the config from the qualifiers of a given <var>config</var>.
|
||||||
* @param config
|
* @param config
|
||||||
@@ -147,6 +154,16 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
|
||||||
mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
|
mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
|
||||||
}
|
}
|
||||||
@@ -446,7 +463,7 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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:
|
* <p/>A match means that:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>This config does not use any qualifier not used by the reference config</li>
|
* <li>This config does not use any qualifier not used by the reference config</li>
|
||||||
@@ -454,29 +471,24 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
|
|||||||
* the reference config.</li>
|
* the reference config.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* @param referenceConfig The reference configuration to test against.
|
* @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) {
|
public boolean isMatchFor(FolderConfiguration referenceConfig) {
|
||||||
int matchCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0 ; i < INDEX_COUNT ; i++) {
|
for (int i = 0 ; i < INDEX_COUNT ; i++) {
|
||||||
ResourceQualifier testQualifier = mQualifiers[i];
|
ResourceQualifier testQualifier = mQualifiers[i];
|
||||||
ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
|
ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
|
||||||
|
|
||||||
// we only care if testQualifier is non null. If it's null, it's a match but
|
// we only care if testQualifier is non null.
|
||||||
// without increasing the matchCount.
|
|
||||||
if (testQualifier != null) {
|
if (testQualifier != null) {
|
||||||
if (referenceQualifier == null) {
|
if (referenceQualifier == null) { // reference config doesn't specify anything
|
||||||
return -1;
|
// for this qualifier so we refuse it.
|
||||||
} else if (testQualifier.equals(referenceQualifier) == false) {
|
return false;
|
||||||
return -1;
|
} else if (testQualifier.isMatchFor(referenceQualifier) == false) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the qualifier match, increment the count
|
|
||||||
matchCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchCount;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -141,6 +141,42 @@ public final class KeyboardStateQualifier extends ResourceQualifier {
|
|||||||
return false;
|
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
|
@Override
|
||||||
public boolean equals(Object qualifier) {
|
public boolean equals(Object qualifier) {
|
||||||
if (qualifier instanceof KeyboardStateQualifier) {
|
if (qualifier instanceof KeyboardStateQualifier) {
|
||||||
|
|||||||
@@ -183,6 +183,38 @@ public final class PixelDensityQualifier extends ResourceQualifier {
|
|||||||
return false;
|
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
|
@Override
|
||||||
public boolean equals(Object qualifier) {
|
public boolean equals(Object qualifier) {
|
||||||
if (qualifier instanceof PixelDensityQualifier) {
|
if (qualifier instanceof PixelDensityQualifier) {
|
||||||
|
|||||||
@@ -62,6 +62,32 @@ public abstract class ResourceQualifier implements Comparable<ResourceQualifier>
|
|||||||
*/
|
*/
|
||||||
public abstract String getFolderSegment(IAndroidTarget target);
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getFolderSegment(null);
|
return getFolderSegment(null);
|
||||||
|
|||||||
@@ -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.FolderConfiguration;
|
||||||
import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
|
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.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.ide.eclipse.adt.internal.resources.manager.files.IAbstractFolder;
|
||||||
import com.android.layoutlib.api.IResourceValue;
|
import com.android.layoutlib.api.IResourceValue;
|
||||||
import com.android.layoutlib.utils.ResourceValue;
|
import com.android.layoutlib.utils.ResourceValue;
|
||||||
@@ -505,79 +506,102 @@ public class ProjectResources implements IResourceRepository {
|
|||||||
* Returns the best matching {@link Resource}.
|
* Returns the best matching {@link Resource}.
|
||||||
* @param resources the list of {@link Resource} to choose from.
|
* @param resources the list of {@link Resource} to choose from.
|
||||||
* @param referenceConfig the {@link FolderConfiguration} to match.
|
* @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,
|
private Resource findMatchingConfiguredResource(List<? extends Resource> resources,
|
||||||
FolderConfiguration referenceConfig) {
|
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>();
|
ArrayList<Resource> matchingResources = new ArrayList<Resource>();
|
||||||
for (int i = 0 ; i < resources.size(); i++) {
|
for (int i = 0 ; i < resources.size(); i++) {
|
||||||
Resource res = resources.get(i);
|
Resource res = resources.get(i);
|
||||||
|
|
||||||
int count = res.getConfiguration().match(referenceConfig);
|
if (res.getConfiguration().isMatchFor(referenceConfig)) {
|
||||||
if (count > currentMax) {
|
|
||||||
matchingResources.clear();
|
|
||||||
matchingResources.add(res);
|
|
||||||
currentMax = count;
|
|
||||||
} else if (count != -1 && count == currentMax) {
|
|
||||||
matchingResources.add(res);
|
matchingResources.add(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have more than one match, we look for the match with the qualifiers with the
|
// if there is only one match, just take it
|
||||||
// highest priority.
|
|
||||||
Resource resMatch = null;
|
|
||||||
if (matchingResources.size() == 1) {
|
if (matchingResources.size() == 1) {
|
||||||
resMatch = matchingResources.get(0);
|
return matchingResources.get(0);
|
||||||
} else if (matchingResources.size() > 1) {
|
} else if (matchingResources.size() == 0) {
|
||||||
// More than one resource with the same number of qualifier match.
|
return null;
|
||||||
// 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);
|
|
||||||
|
|
||||||
// get highest priority qualifiers.
|
// 2. Loop on the qualifiers, and eliminate matches
|
||||||
int m = folder.getConfiguration().getHighestPriorityQualifier(startIndex);
|
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 (referenceQualifier != null) { // no need to check if it's null, since the loop
|
||||||
if (m != -1) {
|
// above will have removed the resources anyway.
|
||||||
if (highest == -1 || m == highest) {
|
boolean found = false;
|
||||||
tmpResources.add(folder);
|
ResourceQualifier bestMatch = null;
|
||||||
highest = m;
|
for (Resource res : matchingResources) {
|
||||||
} else if (m < highest) { // highest priority == lowest index.
|
ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
|
||||||
tmpResources.clear();
|
if (qualifier != null) {
|
||||||
tmpResources.add(folder);
|
// 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
|
// if a resources as a qualifier at the current index, remove all the resources that
|
||||||
// priority qualifiers. Go through the list again looking for the next highest
|
// do not have one.
|
||||||
// priority qualifier.
|
// If there is one, and we have a bestComparable, also check that it's equal to the
|
||||||
startIndex = highest + 1;
|
// 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 (qualifier == null) { // no qualifier? remove the resources
|
||||||
if (matchingResources.size() == tmpResources.size() && highest == -1) {
|
matchingResources.remove(res);
|
||||||
// this means all the resources match with the same qualifiers
|
} else if (bestMatch != null && bestMatch.equals(qualifier) == false) {
|
||||||
// (highest == -1 means we reached the end of the qualifier list)
|
// if there is a best match, only accept the resource if the qualifier
|
||||||
// In this case, we arbitrarily take the first resource.
|
// has the same best value.
|
||||||
matchingResources.clear();
|
matchingResources.remove(res);
|
||||||
matchingResources.add(tmpResources.get(0));
|
} else {
|
||||||
} else {
|
i++;
|
||||||
matchingResources.clear();
|
}
|
||||||
matchingResources.addAll(tmpResources);
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user