blob: 3e60961d78bbc5b12c0ccc322da5f459a0bc2b03 [file] [log] [blame]
package com.xtremelabs.robolectric.res;
import static com.xtremelabs.robolectric.Robolectric.shadowOf;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.shadows.ShadowContextWrapper;
import com.xtremelabs.robolectric.util.I18nException;
import com.xtremelabs.robolectric.util.PropertiesHelper;
import android.R;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
public class ResourceLoader {
private static final FileFilter MENU_DIR_FILE_FILTER = new FileFilter() {
@Override
public boolean accept( File file ) {
return isMenuDirectory( file.getPath() );
}
};
private static final FileFilter LAYOUT_DIR_FILE_FILTER = new FileFilter() {
@Override
public boolean accept( File file ) {
return isLayoutDirectory( file.getPath() );
}
};
private static final FileFilter DRAWABLE_DIR_FILE_FILTER = new FileFilter() {
@Override
public boolean accept( File file ) {
return isDrawableDirectory( file.getPath() );
}
};
private File resourceDir;
private File assetsDir;
private int sdkVersion;
private Class rClass;
private final ResourceExtractor resourceExtractor;
private ViewLoader viewLoader;
private MenuLoader menuLoader;
private PreferenceLoader preferenceLoader;
private final StringResourceLoader stringResourceLoader;
private final PluralResourceLoader pluralResourceLoader;
private final StringArrayResourceLoader stringArrayResourceLoader;
private final AttrResourceLoader attrResourceLoader;
private final ColorResourceLoader colorResourceLoader;
private final DrawableResourceLoader drawableResourceLoader;
private final RawResourceLoader rawResourceLoader;
private final DimenResourceLoader dimenResourceLoader;
private final IntegerResourceLoader integerResourceLoader;
private boolean isInitialized = false;
private boolean strictI18n = false;
private String locale="";
private final Set<Integer> ninePatchDrawableIds = new HashSet<Integer>();
public ResourceLoader( int sdkVersion, Class rClass, File resourceDir, File assetsDir ) throws Exception {
this( sdkVersion, rClass, resourceDir, assetsDir, "");
}
public ResourceLoader( int sdkVersion, Class rClass, File resourceDir, File assetsDir, String locale ) throws Exception {
this.sdkVersion = sdkVersion;
this.assetsDir = assetsDir;
this.rClass = rClass;
this.locale = locale;
resourceExtractor = new ResourceExtractor();
if ( rClass != null ) {
resourceExtractor.addLocalRClass( rClass );
}
resourceExtractor.addSystemRClass( R.class );
stringResourceLoader = new StringResourceLoader( resourceExtractor );
pluralResourceLoader = new PluralResourceLoader( resourceExtractor, stringResourceLoader );
stringArrayResourceLoader = new StringArrayResourceLoader( resourceExtractor, stringResourceLoader );
colorResourceLoader = new ColorResourceLoader( resourceExtractor );
attrResourceLoader = new AttrResourceLoader( resourceExtractor );
drawableResourceLoader = new DrawableResourceLoader( resourceExtractor, resourceDir );
rawResourceLoader = new RawResourceLoader( resourceExtractor, resourceDir );
dimenResourceLoader = new DimenResourceLoader( resourceExtractor );
integerResourceLoader = new IntegerResourceLoader( resourceExtractor );
this.resourceDir = resourceDir;
}
public void setStrictI18n( boolean strict ) {
this.strictI18n = strict;
if ( viewLoader != null ) {
viewLoader.setStrictI18n( strict );
}
if ( menuLoader != null ) {
menuLoader.setStrictI18n( strict );
}
if ( preferenceLoader != null ) {
preferenceLoader.setStrictI18n( strict );
}
}
public boolean getStrictI18n() {
return strictI18n;
}
private void init() {
if ( isInitialized ) {
return;
}
try {
if ( resourceDir != null ) {
viewLoader = new ViewLoader( resourceExtractor, attrResourceLoader );
menuLoader = new MenuLoader( resourceExtractor, attrResourceLoader );
preferenceLoader = new PreferenceLoader( resourceExtractor );
viewLoader.setStrictI18n( strictI18n );
menuLoader.setStrictI18n( strictI18n );
preferenceLoader.setStrictI18n( strictI18n );
File systemResourceDir = getSystemResourceDir( getPathToAndroidResources() );
File localValueResourceDir = getValueResourceDir( resourceDir );
File systemValueResourceDir = getValueResourceDir( systemResourceDir );
File preferenceDir = getPreferenceResourceDir( resourceDir );
loadStringResources( localValueResourceDir, systemValueResourceDir );
loadPluralsResources( localValueResourceDir, systemValueResourceDir );
loadValueResources( localValueResourceDir, systemValueResourceDir );
loadDimenResources( localValueResourceDir, systemValueResourceDir );
loadIntegerResource( localValueResourceDir, systemValueResourceDir );
loadViewResources( systemResourceDir, resourceDir );
loadMenuResources( resourceDir );
loadDrawableResources( resourceDir );
loadPreferenceResources( preferenceDir );
listNinePatchResources(ninePatchDrawableIds, resourceDir);
} else {
viewLoader = null;
menuLoader = null;
preferenceLoader = null;
}
} catch ( I18nException e ) {
throw e;
} catch ( Exception e ) {
throw new RuntimeException( e );
}
isInitialized = true;
}
private File getSystemResourceDir( String pathToAndroidResources ) {
return pathToAndroidResources != null ? new File( pathToAndroidResources ) : null;
}
private void loadStringResources( File localResourceDir, File systemValueResourceDir ) throws Exception {
DocumentLoader stringResourceDocumentLoader = new DocumentLoader( this.stringResourceLoader );
loadValueResourcesFromDirs( stringResourceDocumentLoader, localResourceDir, systemValueResourceDir );
}
private void loadPluralsResources( File localResourceDir, File systemValueResourceDir ) throws Exception {
DocumentLoader stringResourceDocumentLoader = new DocumentLoader( this.pluralResourceLoader );
loadValueResourcesFromDirs( stringResourceDocumentLoader, localResourceDir, systemValueResourceDir );
}
private void loadValueResources( File localResourceDir, File systemValueResourceDir ) throws Exception {
DocumentLoader valueResourceLoader = new DocumentLoader( stringArrayResourceLoader, colorResourceLoader,
attrResourceLoader );
loadValueResourcesFromDirs( valueResourceLoader, localResourceDir, systemValueResourceDir );
}
private void loadDimenResources( File localResourceDir, File systemValueResourceDir ) throws Exception {
DocumentLoader dimenResourceDocumentLoader = new DocumentLoader( this.dimenResourceLoader );
loadValueResourcesFromDirs( dimenResourceDocumentLoader, localResourceDir, systemValueResourceDir );
}
private void loadIntegerResource( File localResourceDir, File systemValueResourceDir ) throws Exception {
DocumentLoader integerResourceDocumentLoader = new DocumentLoader( this.integerResourceLoader );
loadValueResourcesFromDirs( integerResourceDocumentLoader, localResourceDir, systemValueResourceDir );
}
private void loadViewResources( File systemResourceDir, File xmlResourceDir ) throws Exception {
DocumentLoader viewDocumentLoader = new DocumentLoader( viewLoader );
loadLayoutResourceXmlSubDirs( viewDocumentLoader, xmlResourceDir, false );
loadLayoutResourceXmlSubDirs( viewDocumentLoader, systemResourceDir, true );
}
private void loadMenuResources( File xmlResourceDir ) throws Exception {
DocumentLoader menuDocumentLoader = new DocumentLoader( menuLoader );
loadMenuResourceXmlDirs( menuDocumentLoader, xmlResourceDir );
}
private void loadDrawableResources( File xmlResourceDir ) throws Exception {
DocumentLoader drawableDocumentLoader = new DocumentLoader( drawableResourceLoader );
loadDrawableResourceXmlDirs( drawableDocumentLoader, xmlResourceDir );
}
private void loadPreferenceResources( File xmlResourceDir ) throws Exception {
if ( xmlResourceDir.exists() ) {
DocumentLoader preferenceDocumentLoader = new DocumentLoader( preferenceLoader );
preferenceDocumentLoader.loadResourceXmlDir( xmlResourceDir );
}
}
private void loadLayoutResourceXmlSubDirs( DocumentLoader layoutDocumentLoader, File xmlResourceDir, boolean isSystem )
throws Exception {
if ( xmlResourceDir != null ) {
layoutDocumentLoader.loadResourceXmlDirs( isSystem, xmlResourceDir.listFiles( LAYOUT_DIR_FILE_FILTER ) );
}
}
private void loadMenuResourceXmlDirs( DocumentLoader menuDocumentLoader, File xmlResourceDir ) throws Exception {
if ( xmlResourceDir != null ) {
menuDocumentLoader.loadResourceXmlDirs( xmlResourceDir.listFiles( MENU_DIR_FILE_FILTER ) );
}
}
private void loadDrawableResourceXmlDirs( DocumentLoader drawableResourceLoader, File xmlResourceDir ) throws Exception {
if ( xmlResourceDir != null ) {
drawableResourceLoader.loadResourceXmlDirs( xmlResourceDir.listFiles( DRAWABLE_DIR_FILE_FILTER ) );
}
}
private void loadValueResourcesFromDirs( DocumentLoader documentLoader, File localValueResourceDir,
File systemValueResourceDir ) throws Exception {
loadValueResourcesFromDir( documentLoader, localValueResourceDir );
loadSystemResourceXmlDir( documentLoader, systemValueResourceDir );
}
private void loadValueResourcesFromDir( DocumentLoader documentloader, File xmlResourceDir ) throws Exception {
if ( xmlResourceDir != null ) {
documentloader.loadResourceXmlDir( xmlResourceDir );
}
}
private void loadSystemResourceXmlDir( DocumentLoader documentLoader, File stringResourceDir ) throws Exception {
if ( stringResourceDir != null ) {
documentLoader.loadSystemResourceXmlDir( stringResourceDir );
}
}
private File getValueResourceDir( File xmlResourceDir ) {
String valuesDir = "values";
if( !TextUtils.isEmpty( locale ) ){
valuesDir += "-"+ locale;
}
File result = ( xmlResourceDir != null ) ? new File( xmlResourceDir, valuesDir ) : null;
if( result != null && !result.exists() ){
throw new RuntimeException("Couldn't find value resource directory: " + result.getAbsolutePath() );
}
return result;
}
private File getPreferenceResourceDir( File xmlResourceDir ) {
return xmlResourceDir != null ? new File( xmlResourceDir, "xml" ) : null;
}
private String getPathToAndroidResources() {
String resFolder = getAndroidResourcePathFromLocalProperties();
if (resFolder == null) {
resFolder = getAndroidResourcePathFromSystemEnvironment();
if (resFolder == null) {
resFolder = getAndroidResourcePathFromSystemProperty();
if (resFolder == null) {
resFolder = getAndroidResourcePathByExecingWhichAndroid();
}
}
}
// Go through last 5 sdk versions looking for resource folders.
if (resFolder != null) {
for (int i = sdkVersion; i >= sdkVersion - 5 && i >= 4; i--) {
File resourcePath = new File(resFolder, getAndroidResourceSubPath(i));
if (resourcePath.exists()) {
return resourcePath.getAbsolutePath();
} else {
System.out.println("WARNING: Unable to find Android resources at: " +
resourcePath.toString() + " continuing.");
}
}
} else {
System.out.println("WARNING: Unable to find path to Android SDK");
}
return null;
}
private String getAndroidResourcePathFromLocalProperties() {
// Hand tested
// This is the path most often taken by IntelliJ
File rootDir = resourceDir.getParentFile();
String localPropertiesFileName = "local.properties";
File localPropertiesFile = new File( rootDir, localPropertiesFileName );
if ( !localPropertiesFile.exists() ) {
localPropertiesFile = new File( localPropertiesFileName );
}
if ( localPropertiesFile.exists() ) {
Properties localProperties = new Properties();
try {
localProperties.load( new FileInputStream( localPropertiesFile ) );
PropertiesHelper.doSubstitutions( localProperties );
return localProperties.getProperty( "sdk.dir" );
} catch ( IOException e ) {
// fine, we'll try something else
}
}
return null;
}
private String getAndroidResourcePathFromSystemEnvironment() {
// Hand tested
return System.getenv().get( "ANDROID_HOME" );
}
private String getAndroidResourcePathFromSystemProperty() {
// this is used by the android-maven-plugin
return System.getProperty( "android.sdk.path" );
}
private String getAndroidResourcePathByExecingWhichAndroid() {
// Hand tested
// Should always work from the command line. Often fails in IDEs because
// they don't pass the full PATH in the environment
try {
Process process = Runtime.getRuntime().exec( new String[] { "which", "android" } );
String sdkPath = new BufferedReader( new InputStreamReader( process.getInputStream() ) ).readLine();
if ( sdkPath != null && sdkPath.endsWith( "tools/android" ) ) {
return sdkPath.substring(0, sdkPath.indexOf( "tools/android"));
}
} catch ( IOException e ) {
// fine we'll try something else
}
return null;
}
private static String getAndroidResourceSubPath(int version) {
return "platforms/android-" + version + "/data/res";
}
static boolean isLayoutDirectory( String path ) {
return path.contains( File.separator + "layout" );
}
static boolean isDrawableDirectory( String path ) {
return path.contains( File.separator + "drawable" );
}
static boolean isMenuDirectory( String path ) {
return path.contains( File.separator + "menu" );
}
/*
* For tests only...
*/
protected ResourceLoader( StringResourceLoader stringResourceLoader ) {
resourceExtractor = new ResourceExtractor();
this.stringResourceLoader = stringResourceLoader;
pluralResourceLoader = null;
viewLoader = null;
stringArrayResourceLoader = null;
attrResourceLoader = null;
colorResourceLoader = null;
drawableResourceLoader = null;
rawResourceLoader = null;
dimenResourceLoader = null;
integerResourceLoader = null;
}
public static ResourceLoader getFrom( Context context ) {
ResourceLoader resourceLoader = shadowOf( context.getApplicationContext() ).getResourceLoader();
resourceLoader.init();
return resourceLoader;
}
public String getNameForId( int viewId ) {
init();
return resourceExtractor.getResourceName( viewId );
}
public View inflateView( Context context, int resource, ViewGroup viewGroup ) {
init();
return viewLoader.inflateView( context, resource, viewGroup );
}
public int getColorValue( int id ) {
init();
return colorResourceLoader.getValue( id );
}
public String getStringValue( int id ) {
init();
return stringResourceLoader.getValue( id );
}
public String getPluralStringValue( int id, int quantity ) {
init();
return pluralResourceLoader.getValue( id, quantity );
}
public float getDimenValue( int id ) {
init();
return dimenResourceLoader.getValue( id );
}
public int getIntegerValue( int id ) {
init();
return integerResourceLoader.getValue( id );
}
public boolean isDrawableXml( int resourceId ) {
init();
return drawableResourceLoader.isXml( resourceId );
}
public boolean isAnimatableXml( int resourceId ) {
init();
return drawableResourceLoader.isAnimationDrawable( resourceId );
}
public int[] getDrawableIds( int resourceId ) {
init();
return drawableResourceLoader.getDrawableIds( resourceId );
}
public Drawable getXmlDrawable( int resourceId ) {
return drawableResourceLoader.getXmlDrawable( resourceId );
}
public Drawable getAnimDrawable( int resourceId ) {
return getInnerRClassDrawable( resourceId, "$anim", AnimationDrawable.class );
}
public Drawable getColorDrawable( int resourceId ) {
return getInnerRClassDrawable( resourceId, "$color", ColorDrawable.class );
}
@SuppressWarnings("rawtypes")
private Drawable getInnerRClassDrawable( int drawableResourceId, String suffix, Class returnClass ) {
ShadowContextWrapper shadowApp = Robolectric.shadowOf( Robolectric.application );
Class rClass = shadowApp.getResourceLoader().getLocalRClass();
// Check to make sure there is actually an R Class, if not
// return just a BitmapDrawable
if ( rClass == null ) {
return null;
}
// Load the Inner Class for interrogation
Class animClass = null;
try {
animClass = Class.forName( rClass.getCanonicalName() + suffix );
} catch ( ClassNotFoundException e ) {
return null;
}
// Try to find the passed in resource ID
try {
for ( Field field : animClass.getDeclaredFields() ) {
if ( field.getInt( animClass ) == drawableResourceId ) {
return ( Drawable ) returnClass.newInstance();
}
}
} catch ( Exception e ) {
}
return null;
}
public boolean isNinePatchDrawable(int drawableResourceId) {
return ninePatchDrawableIds.contains(drawableResourceId);
}
/**
* Returns a collection of resource IDs for all nine-patch drawables
* in the project.
*
* @param resourceIds
* @param dir
*/
private void listNinePatchResources(Set<Integer> resourceIds, File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory() && isDrawableDirectory(f.getPath())) {
listNinePatchResources(resourceIds, f);
} else {
String name = f.getName();
if (name.endsWith(".9.png")) {
String[] tokens = name.split("\\.9\\.png$");
resourceIds.add(resourceExtractor.getResourceId("@drawable/" + tokens[0]));
}
}
}
}
}
public InputStream getRawValue( int id ) {
init();
return rawResourceLoader.getValue( id );
}
public String[] getStringArrayValue( int id ) {
init();
return stringArrayResourceLoader.getArrayValue( id );
}
public void inflateMenu( Context context, int resource, Menu root ) {
init();
menuLoader.inflateMenu( context, resource, root );
}
public PreferenceScreen inflatePreferences( Context context, int resourceId ) {
init();
return preferenceLoader.inflatePreferences( context, resourceId );
}
public File getAssetsBase() {
return assetsDir;
}
@SuppressWarnings("rawtypes")
public Class getLocalRClass() {
return rClass;
}
public void setLocalRClass( Class clazz ) {
rClass = clazz;
}
public ResourceExtractor getResourceExtractor() {
return resourceExtractor;
}
public ViewLoader.ViewNode getLayoutViewNode( String layoutName ) {
return viewLoader.viewNodesByLayoutName.get( layoutName );
}
public void setLayoutQualifierSearchPath( String... locations ) {
init();
viewLoader.setLayoutQualifierSearchPath( locations );
}
}