Skip to content

Instantly share code, notes, and snippets.

@MajeurAndroid
Created May 21, 2020 22:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MajeurAndroid/a51869e826b9a283a173b65e923857f8 to your computer and use it in GitHub Desktop.
Save MajeurAndroid/a51869e826b9a283a173b65e923857f8 to your computer and use it in GitHub Desktop.
package com.majeur.launcher.data;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Log;
import com.majeur.launcher.R;
import com.majeur.launcher.preference.PreferenceHelper;
import com.majeur.launcher.util.BitmapUtils;
import com.majeur.launcher.util.Constants;
import com.majeur.launcher.util.Matrix;
import com.majeur.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
/**
* Created by MajeurAndroid on 10/10/14.
*/
public class ThemeEngine {
private static final String TAG = ThemeEngine.class.getSimpleName();
private static final String CATEGORY_APEX_THEME = "com.anddoes.launcher.THEME";
private static final String COMPONENT = "component";
private static final String RES_DRAWABLE = "drawable";
private static final String RES_XML = "xml";
private static final String RES_BOOL = "bool";
private static final String RES_ARRAY = "array";
private static final String APPFILTER = "appfilter";
private static final String ICONS = "icon_pack";
private static final String ATTR_SUPPORT_ICON_PACK = "config_iconpack";
private static final String ATTR_ITEM = "item";
private static final String ATTR_SCALE = "scale";
private static final String ATTR_FACTOR = "factor";
private static final String ATTR_ICON_BACK = "iconback";
private static final String ATTR_ICON_MASK = "iconmask";
private static final String ATTR_ICON_UPON = "iconupon";
private static final String[] ATTR_IMGS = {"img1", "img2", "img3", "img4", "img5"};
private static final Matrix sMatrix = new Matrix();
private Application mApplication;
private boolean mIsOperational;
private int mIconSize;
private List<ComponentName> mAppFilterComponentNames = new ArrayList<ComponentName>();
private List<String> mAppFilterDrawableStrings = new ArrayList<String>();
private List<String> mAppFilterIconsBack = new ArrayList<String>(5);
private List<String> mAppFilterIconsMask = new ArrayList<String>(5);
private List<String> mAppFilterIconsUpon = new ArrayList<String>(5);
private float mAppFilterScaleFactor = 1f;
private boolean mSupportIconBack;
private boolean mSupportIconMask;
private boolean mSupportIconUpon;
private boolean mMultipleIconBack;
private boolean mMultipleIconMask;
private boolean mMultipleIconUpon;
private Resources mIconPackResources;
private String mIconPackPackageName;
private Random mRandom = new Random();
private Paint mPaint;
public ThemeEngine(Application application) {
mApplication = application;
mIconSize = application.getResources().getDimensionPixelSize(R.dimen.workspace_item_icon_size);
initializeIconPack();
}
public boolean assertPackageIsCompatible(String iconPackPackageName) {
List<IconPackRetriever.IconPackInfo> iconPackInfoList = new IconPackRetriever(mApplication).loadIconPacksInfo();
for (IconPackRetriever.IconPackInfo iconPackInfo : iconPackInfoList)
if (TextUtils.equals(iconPackInfo.packageName, iconPackPackageName))
return true;
return false;
}
public Bitmap getIconInPack(Resources iconPackResources, String pkgName, String resName) {
int id = iconPackResources.getIdentifier(resName, RES_DRAWABLE, pkgName);
return id != 0 ? BitmapFactory.decodeResource(iconPackResources, id,
BitmapUtils.getOptimalBitmapOptions(iconPackResources, id, mIconSize)) : null;
}
public Bitmap getSpecialIcon(String iconPackPackage, String iconResName) {
Resources localResources;
try {
localResources = mApplication
.createPackageContext(iconPackPackage, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
int id = localResources.getIdentifier(iconResName, RES_DRAWABLE, iconPackPackage);
return id != 0 ?
BitmapFactory.decodeResource(localResources, id, BitmapUtils.getOptimalBitmapOptions(localResources, id, mIconSize))
: null;
}
public String getIconPackPackageName() {
return PreferenceHelper.preferences().getString(Constants.PREF_ICON_PACK_PKG_NAME, null);
}
public void setIconPack(String packageName) {
if (packageName == null)
PreferenceHelper.preferences()
.edit()
.remove(Constants.PREF_ICON_PACK_PKG_NAME)
.apply();
else {
PreferenceHelper.preferences()
.edit()
.putString(Constants.PREF_ICON_PACK_PKG_NAME, packageName)
.apply();
}
initializeIconPack();
}
public boolean isOperational() {
return mIsOperational;
}
private void initializeIconPack() {
try {
prepareIconPackOrThrow();
mIsOperational = true;
} catch (Exception e) {
//e.printStackTrace();
PreferenceHelper.preferences()
.edit()
.remove(Constants.PREF_ICON_PACK_PKG_NAME)
.apply();
mIsOperational = false;
}
}
private void prepareIconPackOrThrow() throws Exception {
mAppFilterComponentNames.clear();
mAppFilterDrawableStrings.clear();
mAppFilterIconsBack.clear();
mAppFilterIconsMask.clear();
mAppFilterIconsUpon.clear();
Context localContext;
mIconPackPackageName = getIconPackPackageName();
if (mIconPackPackageName == null)
throw new NullPointerException("Icon pack packageName is null");
// throws NameNotFoundException
localContext = mApplication.createPackageContext(mIconPackPackageName, Context.CONTEXT_IGNORE_SECURITY);
mIconPackResources = localContext.getResources();
int identifier = mIconPackResources.getIdentifier(APPFILTER, RES_XML, mIconPackPackageName);
// can throw InvalidResIdException if id is 0 (eg. xml doesn't exist)
XmlPullParser appFilterPullParser = mIconPackResources.getXml(identifier);
// throws XmlPullParserException and IOException
int eventType = appFilterPullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
String name = appFilterPullParser.getName();
switch (name) {
case ATTR_ITEM:
String component = appFilterPullParser.getAttributeValue(null, COMPONENT);
String drawableName = appFilterPullParser.getAttributeValue(null, RES_DRAWABLE);
if (component != null && drawableName != null) {
try {
ComponentName cn = getComponentNameFromXmlAttribute(component);
mAppFilterComponentNames.add(cn);
mAppFilterDrawableStrings.add(drawableName);
} catch (StringIndexOutOfBoundsException e) {
Log.w(TAG, "Invalid flatten ComponentName: " + component);
}
}
break;
case ATTR_ICON_BACK:
for (String attribute : ATTR_IMGS) {
String s = appFilterPullParser.getAttributeValue(null, attribute);
if (s != null) {
mAppFilterIconsBack.add(s);
}
}
break;
case ATTR_ICON_MASK:
for (String attribute : ATTR_IMGS) {
String s = appFilterPullParser.getAttributeValue(null, attribute);
if (s != null)
mAppFilterIconsMask.add(s);
}
break;
case ATTR_ICON_UPON:
for (String attribute : ATTR_IMGS) {
String s = appFilterPullParser.getAttributeValue(null, attribute);
if (s != null)
mAppFilterIconsUpon.add(s);
}
break;
case ATTR_SCALE:
String s = appFilterPullParser.getAttributeValue(null, ATTR_FACTOR);
if (s != null)
mAppFilterScaleFactor = Float.parseFloat(s);
break;
}
}
eventType = appFilterPullParser.next();
}
mSupportIconBack = mAppFilterIconsBack.size() > 0;
mSupportIconMask = mAppFilterIconsMask.size() > 0;
mSupportIconUpon = mAppFilterIconsUpon.size() > 0;
mMultipleIconBack = mAppFilterIconsBack.size() > 1;
mMultipleIconMask = mAppFilterIconsMask.size() > 1;
mMultipleIconUpon = mAppFilterIconsUpon.size() > 1;
setPaints();
}
private ComponentName getComponentNameFromXmlAttribute(String xmlAttribute) {
String s = xmlAttribute.substring(14, xmlAttribute.length() - 1);
return ComponentName.unflattenFromString(s);
}
private void setPaints() {
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
mPaint.setAntiAlias(true);
}
private boolean isIconAvailable(ComponentName cn) {
return mAppFilterComponentNames.contains(cn);
}
/**
* Return themed icon if any, else return null
*
* @param componentName Name of the activity represented by the item
* @return Themed icon if any available
*/
public Bitmap peekIconBitmap(ComponentName componentName, int iconSize) {
if (isIconAvailable(componentName)) {
int index = mAppFilterComponentNames.indexOf(componentName);
int resId = mIconPackResources.getIdentifier(mAppFilterDrawableStrings.get(index), RES_DRAWABLE, mIconPackPackageName);
// Return prebuilt icon
if (resId != 0)
return BitmapFactory.decodeResource(mIconPackResources, resId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, resId, iconSize));
}
return null;
}
/**
* Entry point for requesting app/shortcut icon
*
* @param defaultBitmap Default bitmap
* @return Themed icon
*/
public Bitmap loadIconBitmap(Bitmap defaultBitmap) {
return getThemedBitmap(defaultBitmap);
}
/**
* Entry point for requesting app/shortcut icon
*
* @param defaultBitmap Default bitmap
* @return Themed icon
*/
public Bitmap loadShortcutBitmap(Bitmap defaultBitmap) {
return getThemedBitmap(defaultBitmap);
}
// Themes an icon, used if applicationIcon is not supported by icon pack or for shortcut icons
private Bitmap getThemedBitmap(Bitmap appIcon) {
Bitmap iconBack = null;
if (mSupportIconBack) {
String iconBackName;
if (mMultipleIconBack) {
iconBackName = randItem(mAppFilterIconsBack);
} else iconBackName = mAppFilterIconsBack.get(0);
int iconBackId = mIconPackResources.getIdentifier(iconBackName, RES_DRAWABLE, mIconPackPackageName);
if (iconBackId != 0)
iconBack = BitmapFactory.decodeResource(mIconPackResources, iconBackId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, iconBackId, mIconSize));
}
Bitmap iconUpon = null;
if (mSupportIconUpon) {
String iconUponName;
if (mMultipleIconUpon) {
iconUponName = randItem(mAppFilterIconsUpon);
} else iconUponName = mAppFilterIconsUpon.get(0);
int iconUponId = mIconPackResources.getIdentifier(iconUponName, RES_DRAWABLE, mIconPackPackageName);
if (iconUponId != 0)
iconUpon = BitmapFactory.decodeResource(mIconPackResources, iconUponId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, iconUponId, mIconSize));
}
Bitmap iconMask = null;
if (mSupportIconMask) {
String iconMaskName;
if (mMultipleIconMask) {
iconMaskName = randItem(mAppFilterIconsMask);
} else iconMaskName = mAppFilterIconsMask.get(0);
int iconMaskId = mIconPackResources.getIdentifier(iconMaskName, RES_DRAWABLE, mIconPackPackageName);
if (iconMaskId != 0)
iconMask = BitmapFactory.decodeResource(mIconPackResources, iconMaskId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, iconMaskId, mIconSize));
}
Bitmap resultBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
Canvas resultCanvas = new Canvas(resultBitmap);
if (iconBack != null) {
resultCanvas.drawBitmap(iconBack, getResizeMatrix(iconBack, mIconSize, mIconSize), mPaint);
iconBack.recycle();
}
int targetSize = ((int) (mIconSize * mAppFilterScaleFactor));
int offset = (mIconSize / 2) - (targetSize / 2);
if (iconMask != null) {
Bitmap tempBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(tempBitmap);
canvas.drawBitmap(appIcon, getResizeTranslateMatrix(appIcon, targetSize, targetSize, offset, offset), mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
canvas.drawBitmap(iconMask, getResizeMatrix(iconMask, mIconSize, mIconSize), mPaint);
mPaint.setXfermode(null);
iconMask.recycle();
resultCanvas.drawBitmap(tempBitmap, 0, 0, mPaint);
tempBitmap.recycle();
} else {
resultCanvas.drawBitmap(appIcon, getResizeTranslateMatrix(appIcon, targetSize, targetSize, offset, offset), mPaint);
}
if (iconUpon != null) {
resultCanvas.drawBitmap(iconUpon, getResizeMatrix(iconUpon, mIconSize, mIconSize), mPaint);
iconUpon.recycle();
}
return resultBitmap;
}
private <T> T randItem(List<T> list) {
return list.get(mRandom.nextInt(list.size()));
}
private Matrix getResizeMatrix(Bitmap bm, int newWidth, int newHeight) {
return getResizeTranslateMatrix(bm, newWidth, newHeight, 0, 0);
}
private Matrix getResizeTranslateMatrix(Bitmap bm, int newWidth, int newHeight, float dx, float dy) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
sMatrix.reset();
sMatrix.postScale(scaleWidth, scaleHeight);
sMatrix.postTranslate(dx, dy);
return sMatrix;
}
public static class IconPackRetriever {
private Context mContext;
private PackageManager mPackageManager;
public static IconPackRetriever newInstance(Context context) {
return new IconPackRetriever(context);
}
private IconPackRetriever(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
}
public List<IconPackInfo> loadIconPacksInfo() {
final Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(CATEGORY_APEX_THEME);
List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(intent, 0);
List<IconPackInfo> iconPackInfoList = new LinkedList<>();
for (ResolveInfo resolveInfo : resolveInfoList) {
if (supportsIconPack(resolveInfo)) {
IconPackInfo iconPackInfo = new IconPackInfo();
iconPackInfo.packageName = resolveInfo.activityInfo.packageName;
iconPackInfo.icon = resolveInfo.activityInfo.loadIcon(mPackageManager);
iconPackInfo.label = resolveInfo.activityInfo.loadLabel(mPackageManager).toString();
iconPackInfoList.add(iconPackInfo);
}
}
return iconPackInfoList;
}
private boolean supportsIconPack(ResolveInfo resolveInfo) {
Resources localResources;
try {
localResources = mContext
.createPackageContext(resolveInfo.activityInfo.packageName, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return false;
}
int id = localResources.getIdentifier(ATTR_SUPPORT_ICON_PACK, RES_BOOL, resolveInfo.activityInfo.packageName);
return id != 0 && localResources.getBoolean(id);
}
public String[] getIconNamesForPack(String packageName) {
Resources localResources;
try {
localResources = mContext
.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
int id = localResources.getIdentifier(ICONS, RES_ARRAY, packageName);
return id != 0 ? assertedArray(packageName, localResources.getStringArray(id)) : null;
}
private String[] assertedArray(String iconPackPackageName, String[] drawableNames) {
Resources localResources;
try {
localResources = mContext.createPackageContext(iconPackPackageName, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
List<String> list = ArrayUtils.asList(drawableNames);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext())
if (localResources.getIdentifier(iterator.next(), RES_DRAWABLE, iconPackPackageName) == 0)
iterator.remove();
return list.toArray(new String[list.size()]);
}
public static class IconPackInfo {
public String packageName;
public Drawable icon;
public String label;
}
}
}
@khatauli
Copy link

Hi MajeurAndroid, can you please share the implementation of BitmapUtils.getOptimalBitmapOptions

@MajeurAndroid
Copy link
Author

If I recall correctly, this was more and less what is detailed in this developer hint.

@khatauli
Copy link

Thanks. Also, between PeekIconBitmap, and loadIconBitmap methods, which one is the primary method to call from outside. Do you have some example of the calls. For example, given a packageName of an app, how do I get its bitmap from ThemeEngine?

@MajeurAndroid
Copy link
Author

Javadoc of both methods should answer your question. Here is a snippet of how data loading object was interacting with ThemeEngine to retrieve a themed icon. If this can help you:

    protected Bitmap loadApplicationIcon(ComponentName componentName) {
        Bitmap bitmap = mIconCache.getIcon(componentName);
        if (bitmap == null) {
            bitmap = makeApplicationIcon(componentName);
            mIconCache.putIcon(componentName, bitmap);
        }
        return bitmap;
    }

    private Bitmap makeApplicationIcon(ComponentName componentName) {
        if (mThemeEngine.isOperational()) {
            Bitmap icon = mThemeEngine.peekIconBitmap(componentName, mWorkspaceIconSize);

            if (icon != null) {
                return icon;
            } else {
                Bitmap defaultIcon = getApplicationIconFromRes(componentName);

                icon = mThemeEngine.loadIconBitmap(defaultIcon);

                defaultIcon.recycle();

                return icon;
            }

        } else {
            return getApplicationIconFromRes(componentName);
        }
    }

    private Bitmap getApplicationIconFromRes(ComponentName componentName) {
        try {
            Context appContext = getContext().createPackageContext(componentName.getPackageName(), Context.CONTEXT_IGNORE_SECURITY);

            ActivityInfo info = mPackageManager.getActivityInfo(componentName, 0);
            int iconId = info.icon;

            if (iconId == 0)
                iconId = info.applicationInfo.icon;

            if (iconId == 0)
                // We throw a dummy exception to fallback to catch block
                throw new PackageManager.NameNotFoundException("No icon available for this app");

            Bitmap bitmap = BitmapFactory.decodeResource(appContext.getResources(),
                    iconId);
                    /*Utils.getOptimalBitmapOptions(appContext.getResources(),
                            iconId,
                            mWorkspaceIconSize));
                    */
            if (bitmap == null) // Probably an xml drawable
                bitmap = getBitmapFromNonBitmapDrawable(appContext.getResources().getDrawable(iconId));

            return bitmap;

        } catch (PackageManager.NameNotFoundException | IllegalArgumentException e) {
            e.printStackTrace();
            return BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_android_package);
        }
    }

    private Bitmap getBitmapFromNonBitmapDrawable(Drawable drawable) {
        if (drawable instanceof BitmapDrawable) // Last try
            return ((BitmapDrawable) drawable).getBitmap();

        Bitmap bitmap = Bitmap.createBitmap(mWorkspaceIconSize, mWorkspaceIconSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, mWorkspaceIconSize, mWorkspaceIconSize);
        drawable.draw(canvas);

        return bitmap;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment