/* Copyright (c) 2002-2011 by XMLVM.org
*
* Project Info: http://www.xmlvm.org
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package android.internal;
import java.util.ArrayList;
import java.util.List;
import android.content.res.Configuration;
/**
*
* Implementation of Android's algorithm used to determine the folders to search
* for resources. The folders to search depend on the configuration of the
* device.
*
*/
public class ResourceFolderSelector {
/**
*
* Determine the resource folders which have to be accessed to obtain
* resources. Folders holding alternative resources are matched against the
* device configuration to determine the folders to consider. The algorithm
* used to determine the folders is specified in the Android SDK's
* documentation.
*
* @param folders
* The folders to match against the device configuration. The
* list must only contain folders holding resources of one
* resource type.
*
* @param configuration
* The device configuration to be matched against the folder
* names.
*
* @param density
* The density of the display.
*
* @return A list holding all folders to be considered. The order of the
* returned list is significant. The folders have to be searched for
* a particular resource in the order as they are returned.
*
*/
public List<String> getResourceFolders(List<String> folders, Configuration configuration,
int density) {
List<String> result = new ArrayList<String>();
List<Folder> parsedFolders = new ArrayList<Folder>();
for (String folder : folders) {
parsedFolders.add(new Folder(folder));
}
// Step 1: Remove folders contradicting the device specification
removeContradictions(parsedFolders, configuration);
// Call internal version of getResourceFolders recursively
getResourceFolders(result, parsedFolders, configuration, density);
return result;
}
/**
*
* Determine the resource folders which have to be accessed to obtain
* resources. Folders holding alternative resources are matched against the
* device configuration to determine the folders to consider. The algorithm
* used to determine the folders is specified in the Android SDK's
* documentation. This method is gets called internally by
* {@link #getResourceFolders(List, DeviceProperties)}.
*
* @param result
* The list of folders found. The order of the list is
* significant. The folders have to be searched for a particular
* resource in the order as they are stored.
*
* @param folders
* The folders to match against the device specification. The
* list must only contain folders holding resources of one
* resource type. Folders a now represented by Folder objects
* holding the parsed resource modifiers.
*
* @param configuration
* The device configuration to be matched against the folder
* names.
*
* @param density
* The density of the display.
*
* @return A list holding all folders to be considered. The order of the
* returned list is significant. The folders have to be searched for
* a particular resource in the order as they are returned.
*
*/
private void getResourceFolders(List<String> result, List<Folder> folders,
Configuration configuration, int density) {
// Step 2 to 5: Test for the presence of qualifiers and remove all
// folders which do not include the qualifier detected in other folders
// Testing is done in the orders as defined by the Android
// specifiaction.
// TODO: Currently not all properties are supported.
// Test the locale - first including region. If that does not match test
// without region.
List<Folder> resultFolders = new ArrayList<Folder>(folders);
int match = testConfigurationPresent(Folder.LOCALE_MATCHER, resultFolders, configuration);
if (match > Folder.Matcher.NO_MATCH) {
removeFoldersByConfiguration(Folder.LOCALE_MATCHER, resultFolders, configuration,
match == Folder.Matcher.FULL_MATCH);
}
// Test screen size
if (testConfigurationPresent(Folder.SCREENSIZE_MATCHER, resultFolders, configuration) == Folder.Matcher.FULL_MATCH) {
removeFoldersByConfiguration(Folder.SCREENSIZE_MATCHER, resultFolders, configuration,
true);
}
// Test screen aspect
if (testConfigurationPresent(Folder.SCREENASPECT_MATCHER, resultFolders, configuration) == Folder.Matcher.FULL_MATCH) {
removeFoldersByConfiguration(Folder.SCREENASPECT_MATCHER, resultFolders, configuration,
true);
}
// Test screen orientation
if (testConfigurationPresent(Folder.ORIENTATION_MATCHER, resultFolders, configuration) == Folder.Matcher.FULL_MATCH) {
removeFoldersByConfiguration(Folder.ORIENTATION_MATCHER, resultFolders, configuration,
true);
}
// Test screen density. If no matching qualifier is present use best
// match. Best match is the next density higher than the devices
// density.
// If also no higher density is found try lower densities. Last guess is
// nodpi
int matchedDensity = getBestDensity(resultFolders, density);
if (matchedDensity != -1) {
removeFoldersByDensity(resultFolders, matchedDensity);
} else {
removeFoldersByDensity(resultFolders, Density.DENSITY_NONE);
}
// Now resultFolders should hold at most one entry
if (resultFolders.size() > 0) {
result.add(resultFolders.get(0).getName());
// Recursively determine folders with the found folder removed
folders.remove(resultFolders.get(0));
if (folders.size() > 0) {
getResourceFolders(result, folders, configuration, density);
}
}
}
/**
*
* Removes all folders from the provided list of folders which contradict
* the current device configuration. The folder's parsed resource modifiers
* are compared with the device configuration values. As soon as a folder
* holds a contradicting modifier it will be deleted from the list of
* folders. Density modifiers never lead to contradictions.
*
* @param folders
* The folders to be checked.
*
* @param configuration
* The device configuration to be matched against the folder
* names.
*
*/
private void removeContradictions(List<Folder> folders, Configuration configuration) {
List<Folder> contradictions = new ArrayList<Folder>();
// Collect contradictions
for (Folder folder : folders) {
if (folder.contradicts(configuration)) {
contradictions.add(folder);
}
}
// Remove contradictions from input list
folders.removeAll(contradictions);
}
/**
*
* Tests whether one of the folders has a particular value specified which
* matches the given configuration.
*
* @param matcher
* The Matcher implementation used to test for the value.
*
* @param folders
* The folders to be tested for a matching orientation.
*
* @param configuration
* The configuration providing the orientation to look for.
*
* @return NO_MATCH, PARTIAL_MATCH or FULL_MATCH depending on the matching
* result.
*
*/
private int testConfigurationPresent(Folder.Matcher matcher, List<Folder> folders,
Configuration configuration) {
int match = Folder.Matcher.NO_MATCH;
for (Folder folder : folders) {
int i = matcher.matches(folder, configuration);
// Return in case of full match
if (i == Folder.Matcher.FULL_MATCH) {
return i;
}
// Record partial match
if (i > match) {
match = i;
}
}
return match;
}
/**
*
* Removes folders from the provided list of folders based on a provided
* configuration. All folders which do not match the configuration using the
* provided matcher will be removed.
*
* @param matcher
* The matcher used to match the folder against the configuration
*
* @param folders
* The list of folders to remove not matching folders from.
*
* @param configuration
* The configuration providing the orientation to match against.
*
* @param fullMatchRequired
* Controls whether a full match is required. In this case
* partial matching folders will be removed as well.
*
*/
private void removeFoldersByConfiguration(Folder.Matcher matcher, List<Folder> folders,
Configuration configuration, boolean fullMatchRequired) {
List<Folder> unmatched = new ArrayList<Folder>();
// Collect unmatching folders
for (Folder folder : folders) {
int match = matcher.matches(folder, configuration);
if (match == Folder.Matcher.NO_MATCH
|| (fullMatchRequired && match != Folder.Matcher.FULL_MATCH)) {
unmatched.add(folder);
}
}
// Remove folders
folders.removeAll(unmatched);
}
/**
*
* Determines the density matching best the device's configuration.
*
* @param folders
* The folders to check for its density modifier.
*
* @param density
* The device's density to match the folders against.
*
* @return The density matching best encoded as an integer value. If no
* density is found, -1 will be returned.
*
*/
private int getBestDensity(List<Folder> folders, int density) {
int[][] densityOrder = {
{ Density.DENSITY_LOW, Density.DENSITY_UNDEFINED, Density.DENSITY_MEDIUM,
Density.DENSITY_HIGH },
{ Density.DENSITY_MEDIUM, Density.DENSITY_UNDEFINED, Density.DENSITY_HIGH,
Density.DENSITY_LOW },
{ Density.DENSITY_HIGH, Density.DENSITY_UNDEFINED, Density.DENSITY_MEDIUM,
Density.DENSITY_LOW } };
// Collect available densities
int densities = 0;
for (Folder folder : folders) {
densities |= (1 << folder.getDensity());
}
// Test densities in the order which best matches the device
// configuration
int[] deviceDensityOrder = densityOrder[density - 1];
for (int i = 0; i < deviceDensityOrder.length; i++) {
if ((densities & (1 << deviceDensityOrder[i])) > 0) {
return deviceDensityOrder[i];
}
}
// Default: Return DENSITY_UNSPECIFIED, this should never be reached
// unless there are no folders to be tested.
return -1;
}
/**
*
* Removes all folders from the provided list of folders which do not
* specify the given density.
*
* @param folders
* The folders to be tested.
*
* @param density
* The density to test for.
*
*/
private void removeFoldersByDensity(List<Folder> folders, int density) {
List<Folder> unmatched = new ArrayList<Folder>();
// Collect unmatching folders
for (Folder folder : folders) {
if (folder.getDensity() != density) {
unmatched.add(folder);
}
}
// Remove unmatching folders
folders.removeAll(unmatched);
}
}