/*******************************************************************************
* Copyright 2013 Alexandros Schillings
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package co.uk.alt236.reflectivedrawableloader;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import co.uk.alt236.reflectivedrawableloader.containers.DrawableResourceContainer;
import co.uk.alt236.reflectivedrawableloader.containers.LruLinkedHashMap;
public final class ReflectiveDrawableLoader {
public static final String ICON_PREFIX_BASE = "ic_";
public static final String ICON_PREFIX_LAUNCHER = ICON_PREFIX_BASE + "launcher_";
public static final String ICON_PREFIX_MENU = ICON_PREFIX_BASE + "menu_";
public static final String ICON_PREFIX_STATUS_BAR = ICON_PREFIX_BASE + "stat_notify_";
public static final String ICON_PREFIX_TAB = ICON_PREFIX_BASE + "tab_";
public static final String ICON_PREFIX_DIALOG = ICON_PREFIX_BASE + "dialog_";
public static final String ICON_PREFIX_LIST = ICON_PREFIX_BASE + "list_";
private static final int CACHE_SIZE = 100;
private static final boolean TIME_LOGGING_ENABLED = false;
private static ReflectiveDrawableLoader instance = null;
private final String TAG = getClass().getName();
private final AtomicBoolean mAddDrawableNameToContainer;
private final AtomicBoolean mLogErrors;
private final ReflectionUtils mReflectionUtils;
private final LruLinkedHashMap<String, Integer> mCache;
private final LruLinkedHashMap<String, Object> mCacheMisses;
private ReflectiveDrawableLoader() {
// We should never be here...
mReflectionUtils = null;
mCache = null;
mAddDrawableNameToContainer = null;
mLogErrors = null;
Log.e(TAG, "ReflectiveDrawableLoader() The default Constructor was called! This should never happen...");
throw new IllegalStateException("The default Constructor was called! This should never happen...");
}
private ReflectiveDrawableLoader(Context context) {
mReflectionUtils = new ReflectionUtils(context.getApplicationContext().getPackageName());
mCache = new LruLinkedHashMap<String, Integer>(CACHE_SIZE, 0.75f);
mCacheMisses = new LruLinkedHashMap<String, Object>(CACHE_SIZE, 0.75f);
mAddDrawableNameToContainer = new AtomicBoolean(false);
mLogErrors = new AtomicBoolean(false);
}
private DrawableResourceContainer fetchDrawableContainer(String drawableName, String color, int fallbackDrawableId){
int res = fetchDrawableId(drawableName, fallbackDrawableId);
return new DrawableResourceContainer((mAddDrawableNameToContainer.get() ? drawableName: null), res, tryColor(color));
}
private synchronized int fetchDrawableId(String drawableName, int fallbackDrawableId){
Integer result = null;
long startTime;
if(TIME_LOGGING_ENABLED){
startTime = System.nanoTime();
}
// Check if its in the known "cache miss" list
if(mCacheMisses.containsKey(drawableName)){
result = fallbackDrawableId;
} else {
result = mCache.get(drawableName);
if(result == null){
result = mReflectionUtils.reflectDrawable(drawableName, fallbackDrawableId, mLogErrors.get());
if(result != null && result != fallbackDrawableId){
mCache.put(drawableName, result);
} else {
// We do not have this drawable, add it in the "miss" cache.
mCacheMisses.put(drawableName, null);
}
}
}
if(TIME_LOGGING_ENABLED){
long endTime = System.nanoTime();
Log.d(TAG, "fetchDrawableId() - Fetched '" + drawableName + "' in " + (endTime - startTime) + "ns");
}
return result;
}
/**
* This is a convenience function which can be used to quickly fetch Dialog Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_DIALOG}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getDialogDrawable(String drawableName, String family, int fallbackDrawableId) {
return getDrawableId(ICON_PREFIX_DIALOG + formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Dialog Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_DIALOG}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getDialogDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId) {
return fetchDrawableContainer(ICON_PREFIX_DIALOG + formatKey(drawableName, family), colorString, fallbackDrawableId);
}
/**
* This function will return {@link DrawableResourceContainer} containing the requested Drawable information
* This function makes no assumptions regarding a Drawable's prefix, so you will need input its full name.
* It is functionally identical to calling getDrawableContainer(drawableName, family, colorString, fallbackDrawableId)
* with the family set to null.
*
* @param drawableName The name of the Drawable to fetch.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getDrawableContainer(String drawableName, String colorString, int fallbackDrawableId){
return getDrawableContainer(drawableName, null, colorString, fallbackDrawableId);
}
/**
* This function will return {@link DrawableResourceContainer} containing the requested Drawable information
* <b>This function makes no assumptions regarding a Drawable's prefix, so you will need input its full name.</b>
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId){
return fetchDrawableContainer(formatKey(drawableName, family), colorString, fallbackDrawableId);
}
/**
* Attempts to retrieve the Id of the requested Drawable.
*
* @param drawableName The name of the Drawable to fetch.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getDrawableId(String drawableName, int fallbackDrawableId){
return getDrawableId(drawableName, null, fallbackDrawableId);
}
/**
* Attempts to retrieve the Id of the requested Drawable.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getDrawableId(String drawableName, String family, int fallbackDrawableId){
return fetchDrawableId(formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Launcher Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_LAUNCHER}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getLauncherDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId) {
return fetchDrawableContainer(ICON_PREFIX_LAUNCHER + formatKey(drawableName, family), colorString, fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Launcher Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_LAUNCHER}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getLauncherDrawableId(String drawableName, String family, int fallbackDrawableId) {
return fetchDrawableId(ICON_PREFIX_LAUNCHER + formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch List Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_LIST}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getListDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId) {
return fetchDrawableContainer(ICON_PREFIX_LIST + formatKey(drawableName, family), colorString, fallbackDrawableId);
}
//
/**
* This is a convenience function which can be used to quickly fetch List Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_LIST}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getListDrawableId(String drawableName, String family, int fallbackDrawableId) {
return fetchDrawableId(ICON_PREFIX_LIST + formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Menu Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_MENU}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getMenuDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId) {
return fetchDrawableContainer(ICON_PREFIX_MENU + formatKey(drawableName, family), colorString, fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Menu Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_MENU}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getMenuDrawableId(String drawableName, String family, int fallbackDrawableId) {
return fetchDrawableId(ICON_PREFIX_MENU + formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Status Bar Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_STATUS_BAR}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getStatusBarDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId) {
return fetchDrawableContainer(ICON_PREFIX_STATUS_BAR + formatKey(drawableName, family), colorString, fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Status Bar Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_STATUS_BAR}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getStatusBarDrawableId(String drawableName, String family, int fallbackDrawableId) {
return fetchDrawableId(ICON_PREFIX_STATUS_BAR + formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Tab Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_TAB}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param colorString The colour used for the colour filter. It has to be in "#FFFFFF" format.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return A {@link DrawableResourceContainer} with the requested Drawable data.
*/
public DrawableResourceContainer getTabDrawableContainer(String drawableName, String family, String colorString, int fallbackDrawableId) {
return fetchDrawableContainer(ICON_PREFIX_TAB + formatKey(drawableName, family), colorString, fallbackDrawableId);
}
/**
* This is a convenience function which can be used to quickly fetch Tab Drawables without
* having to mess around with String concatenation in your code.
* The Drawable filename in the Res folder needs to be prefixed with {@value #ICON_PREFIX_TAB}.
*
* @param drawableName The name of the Drawable to fetch.
* @param family The family (if any) of the variable to fetch. Set to null if no family is needed.
* @param fallbackDrawableId The id of the Drawable to use if the requested one does not exist.
* @return The Id of the Drawable to display.
*/
public int getTabDrawableId(String drawableName, String family, int fallbackDrawableId) {
return fetchDrawableId(ICON_PREFIX_TAB + formatKey(drawableName, family), fallbackDrawableId);
}
/**
* This function will print a list of all drawables this library can see into logcat
* Only useful for debugging.
*/
public void printDrawablesToLogCat(){
mReflectionUtils.logFields(ReflectionUtils.RESOURCE_LOCATION_DRAWABLES);
}
/**
* Enables or disables the addition of the requested Drawable name in the resulting {@link DrawableResourceContainer}
* when requesting a Colorised Drawable.
*
* @param enable - True to enable, false to disable. False by default;
*/
public synchronized void setAddDrawableNameToContainer(boolean enable){
mAddDrawableNameToContainer.set(enable);
}
/**
* Enables or disables the logging of errors in LogCat during operation.
* The errors will be logged as warning.
* Types of errors logged:
* - Reflection Errors
* - Color parsing errors
*
* @param enable - True to enable, false to disable. False by default;
*/
public synchronized void setLogErrors(boolean enable){
mLogErrors.set(enable);
}
private Integer tryColor(String colorString){
if(colorString == null || colorString.length() < 1){
return null;
}
try{
return Color.parseColor(colorString);
} catch (IllegalArgumentException e){
if(mLogErrors.get()){
Log.w(TAG, "tryColor() - IllegalArgumentException while trying to parse color '" + colorString + "'");
}
return null;
}
}
public static String formatKey(String name, String family){
if(family != null && family.length() > 0){
return family.concat("_").concat(name);
} else {
return name;
}
}
public static String formatKey(String prefix, String name, String family){
if(family != null && family.length() > 0){
return prefix.concat(family).concat(formatKey(name, family));
} else {
return prefix.concat(name);
}
}
/**
* Returns an instance of the ReflectiveDrawableLoader
*
* @param context A standard Android context. It cannot be null
* @return The instance of the ReflectiveDrawableLoader
*/
public static ReflectiveDrawableLoader getInstance(Context context) {
if (instance == null) {
synchronized (ReflectiveDrawableLoader .class){
if (instance == null) {
instance = new ReflectiveDrawableLoader (context);
}
}
}
return instance;
}
}