/* 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.content.res;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.internal.AndroidManifest;
import android.internal.Assert;
import android.internal.CommonDeviceAPIFinder;
import android.internal.ConfigurationFactory;
import android.internal.Density;
import android.internal.ResourceFolderSelector;
import android.internal.ResourceParser;
import android.util.DisplayMetrics;
import android.util.Log;
public class Resources {
/** Filename prefix used for IOS specific resources. */
private static final String IOS_PREFIX = "_ios_";
/** The name of the directory holding the application's resources. */
private static final String RES_DIR = "res";
/** The device's configuration */
private Configuration configuration = null;
/** The device's density. */
private int density = Density.DENSITY_UNDEFINED;
/**
* UIImages whose size (in bytes) is less than this threshold will be
* cached.
*/
final private static long DRAWABLE_CACHE_THRESHOLD = 50000;
/**
* The cached folders to search for values resources (string, dimensions,
* ...).
*/
private static List<String> valuesFolders = null;
/** The cached folders to search for layout resources. */
private static List<String> layoutFolders = null;
/** The cached folders to search for drawable resources. */
private static List<String> drawableFolders = null;
/** A map holding the mapping from IDs to variable names. */
private static Map<Integer, String> idToNameMap = new HashMap<Integer, String>();
/** A map holding the mapping from variable names to IDs. */
private static Map<String, Integer> nameToIdMap = new HashMap<String, Integer>();
/** A map holding the mapping from resourceId to Drawable. */
private static Map<Integer, Drawable> drawableMap = new HashMap<Integer, Drawable>();
/**
* A map holding the mapping from resourceId to NSData (representing the
* content of the XML layout file).
*/
private static Map<Integer, String> layoutMap = new HashMap<Integer, String>();
/** A map holding all resources which can be read from the values folders. */
private static Map<Integer, Object> resourceMap = null;
private WeakReference<Context> context;
static {
initResources("attr");
initResources("drawable");
initResources("id");
initResources("layout");
initResources("string");
initResources("array");
initResources("dimen");
initResources("raw");
}
public Resources(Context context) {
this.context = new WeakReference<Context>(context);
};
public Drawable getDrawable(int resourceId) {
Drawable d = drawableMap.get(new Integer(resourceId));
if (d == null) {
// Initialize the folders to search for drawables
if (drawableFolders == null) {
drawableFolders = getResourceFolders("drawable");
}
// Initialze layout folder as well, XML based drawables might be
// stored in a layout folder
if (layoutFolders == null) {
layoutFolders = getResourceFolders("layout");
}
// Get the layout resource's name and determine whether it is
// located
// in the drawable or layout folder
String resourceName = getResourceName(findResourceNameById(resourceId));
boolean usesDrawableFolder = getResourceDirectory(findResourceNameById(resourceId))
.equals("drawable");
// Iterate drawable folders and try to load image
for (String folder : usesDrawableFolder ? drawableFolders : layoutFolders) {
String type = findDrawableType(folder, resourceName);
if (type != null) {
if (type.equals("xml")) {
d = ResourceParser.parseDrawable(getContext(), RES_DIR + "/" + folder + "/"
+ resourceName);
drawableMap.put(new Integer(resourceId), d);
} else {
String path = CommonDeviceAPIFinder.instance().getFileSystem().getApplicationPath() + "/" + RES_DIR + "/"
+ folder + "/" + resourceName + ".png";
// UIImage image = UIImage.imageWithContentsOfFile(path);
// UIImage image = UIImage.imageNamed(RES_DIR + "/" +
// folder
// + "/" + resourceName + ".png");
d = BitmapDrawable.xmlvmCreateWithPath(path);
File f = new File(path);
if (f.length() < DRAWABLE_CACHE_THRESHOLD) {
drawableMap.put(new Integer(resourceId), d);
}
}
break;
}
}
}
return d;
}
private String findDrawableType(String folder, String drawableName) {
String path = RES_DIR + "/" + folder;
if (CommonDeviceAPIFinder.instance().getFileSystem().getPathForResource(drawableName, "png", path) != null) {
return "png";
}
if (CommonDeviceAPIFinder.instance().getFileSystem().getPathForResource(drawableName, "xml", path) != null) {
return "xml";
}
return null;
}
public String getLayout(int resourceId) {
String theFile = layoutMap.get(new Integer(resourceId));
if (theFile == null) {
// Initialize the folders to search for layouts
if (layoutFolders == null) {
layoutFolders = getResourceFolders("layout");
}
// Get the layout resource's name
String resourceName = getResourceName(findResourceNameById(resourceId));
String iosResourceName = IOS_PREFIX + resourceName;
// Iterate the layout folders and try to load the layout from that
// folder
for (String folder : layoutFolders) {
// First try IOS specific resources, if not found use "normal"
// resource
String filePath = CommonDeviceAPIFinder.instance().getFileSystem().getPathForResource(iosResourceName, "xml",
RES_DIR + "/" + folder);
if (filePath == null) {
filePath = CommonDeviceAPIFinder.instance().getFileSystem().getPathForResource(resourceName, "xml",
RES_DIR + "/" + folder);
}
if (filePath != null) {
theFile = filePath;
layoutMap.put(new Integer(resourceId), theFile);
break;
}
}
}
return theFile;
}
public int getIdentifier(String name, String defType, String defPackage) {
String str = name;
int i;
// Remove package
i = str.indexOf(':');
if (i != -1) {
str = str.substring(i + 1);
}
// Prepend resource type if not specified
if (str.indexOf('/') == -1 && defType != null) {
str = defType + "/" + str;
}
return findResourceIdByName(str);
}
public String getResourceEntryName(int resourceId) {
String str = findResourceNameById(resourceId);
return str == null ? null : str.substring(str.indexOf('/'));
}
public String getResourcePackageName(int resourceId) {
return AndroidManifest.getPackageName();
}
public String getResourceTypeName(int resourceId) {
String str = findResourceNameById(resourceId);
return str == null ? null : str.substring(0, str.indexOf('/'));
}
public String getResourceName(int resourceId) {
String str = findResourceNameById(resourceId);
return str == null ? null : AndroidManifest.getPackageName() + ':' + str;
}
private String findResourceNameById(int resourceId) {
String name = idToNameMap.get(new Integer(resourceId));
return name != null ? name : "";
}
private int findResourceIdByName(String resourceName) {
Integer i = nameToIdMap.get(resourceName);
return i != null ? i.intValue() : -1;
}
private static void initResources(String resourceClass) {
try {
String activityPackageName = AndroidManifest.getPackageName();
String rClassName = activityPackageName + ".R$" + resourceClass;
Class<?> rClazz = Class.forName(rClassName);
Field[] fields = rClazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
String fullFieldName = resourceClass + "/" + fields[i].getName();
idToNameMap.put(new Integer(fields[i].getInt(rClazz)), fullFieldName);
nameToIdMap.put(fullFieldName, new Integer(fields[i].getInt(rClazz)));
}
} catch (Throwable t) {
Log.i("Resources",
"Unable to resolve resources for " + AndroidManifest.getPackageName() + ": "
+ resourceClass);
}
}
private String getResourceName(String filePath) {
int i = filePath.lastIndexOf('/');
return i >= 0 ? filePath.substring(i + 1) : filePath;
}
private String getResourceDirectory(String filePath) {
int i = filePath.lastIndexOf('/');
return i >= 0 ? filePath.substring(0, i) : null;
}
/**
* @param id
* @return
*/
public String getString(int id) {
if (resourceMap == null) {
loadValueResources();
}
return (String) resourceMap.get(new Integer(id));
}
public String[] getTextArray(int id) {
if (resourceMap == null) {
loadValueResources();
}
return (String[]) resourceMap.get(new Integer(id));
}
/**
*
* Loads all resources stored in the values folders: String, StringArray,
* Dimensions, etc.. To load the resources all XML files stored in one of
* the values folders are parsed and stored. The folders to search depend on
* the device configuration and the folders available.
*
*/
private void loadValueResources() {
// Initialize the folders to search for strings
if (valuesFolders == null) {
valuesFolders = getResourceFolders("values");
}
resourceMap = new HashMap<Integer, Object>();
// Iterate all suitable values folders
for (String folder : valuesFolders) {
// Get all files from the folder
List<String> allFiles = CommonDeviceAPIFinder.instance().getFileSystem().listDirectory(
CommonDeviceAPIFinder.instance().getFileSystem().getApplicationPath() + "/" + RES_DIR + "/" + folder);
// Iterate all files and parse XML files
for (String file : allFiles) {
if (file.endsWith(".xml")) {
String baseName = file.substring(0, file.length() - 4);
String fullPath = RES_DIR + "/" + folder + "/" + baseName;
ResourceParser.parse(getContext(), fullPath, nameToIdMap, resourceMap);
}
}
}
}
public String getText(int id) {
return getString(id);
}
/**
*
* Get the folders to search for resources of a particular type. The type is
* specified by the folder's prefix (drawable, layout, ...).
*
* @param type
* The resource type to get the folders for.
*
* @return The folders to search for resources.
*
*/
private List<String> getResourceFolders(String type) {
// Get all files and folders below the res folder
List<String> allFiles = CommonDeviceAPIFinder.instance().getFileSystem().listDirectory(
CommonDeviceAPIFinder.instance().getFileSystem().getApplicationPath() + "/" + RES_DIR);
// Get all folders of the
List<String> resourceFolders = new ArrayList<String>();
if (allFiles != null) {
for (String file : allFiles) {
if (file.startsWith(type)) {
resourceFolders.add(file);
}
}
}
// Initialize configuration and density if not already initialized
if (configuration == null) {
configuration = ConfigurationFactory.create();
}
if (density == Density.DENSITY_UNDEFINED) {
density = ConfigurationFactory.getDensity();
}
return new ResourceFolderSelector().getResourceFolders(resourceFolders, configuration,
density);
}
public DisplayMetrics getDisplayMetrics() {
Assert.NOT_IMPLEMENTED();
return null;
}
public Configuration getConfiguration() {
Assert.NOT_IMPLEMENTED();
return null;
}
private Context getContext() {
return context == null ? null : context.get();
}
public float getDimension(int id) {
if (resourceMap == null) {
loadValueResources();
}
return ((Float) resourceMap.get(new Integer(id))).floatValue();
}
// TODO: Proper handling of file name extensions
public InputStream openRawResource(int id) {
String fileName = findResourceNameById(id);
String path = CommonDeviceAPIFinder.instance().getFileSystem().getApplicationPath() + "/" + RES_DIR + "/" + fileName;
try {
return new FileInputStream(path);
} catch (FileNotFoundException e) {
try {
return new FileInputStream(path + ".mp3");
} catch (FileNotFoundException e1) {
return null;
}
}
}
}