Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Implementing icon pack support

I'm trying to implement icon pack support in my application so that the same icon packs which works with Apex, Nova, ADW etc.. Will also work with my application. Using this code, to find the appfilter xml file and then parse it to get the drawable names, I've been able to get this working with free themes:

Context context = createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY);
                Resources res = context.getResources();
                String str = "";
                res.getAssets().list(str);

However with Paid icon packs this doesn't work. The assets returned are 0. I believe this is because of the forward locking present on paid apps on JB+ which makes the assets private. (Outlined here: Accessing assets of other Android app on Jelly Bean)

I haven't been able to find any information on how to support an icon pack and decompiling an icon pack apk I can see no content provider so I can only assume these launcher apps are using a method similar to mine to retrieve the assets.

Is anyone able to give me any info on how this can be done or point me in the right direction?

like image 650
crazyfool Avatar asked Jun 26 '13 10:06

crazyfool


2 Answers

Not an official source, but the Apex Launcher Theme Tutorial notes that

Next, open the appfilter.xml file under the res/xml directory. (Note: This file used to be located under the assets directory, but the new app encryption feature in JellyBean made it impossible for the theme engine to access assets of paid themes.)

So it looks as if there is no special content provider used - the mechanism simply had to be adapted to work with the security mechanism introduced on Jelly Bean.

like image 161
zmarties Avatar answered Oct 15 '22 20:10

zmarties


A bit too late, but here is how I implemented shuch feature in Solid Launcher few years ago. It might not be perfect, but it will give you a basic explanation on how theming works.

ThemeEngine.java:

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;
        }
    }
}

Or here: https://gist.github.com/MajeurAndroid/a51869e826b9a283a173b65e923857f8

like image 37
Pdroid Avatar answered Oct 15 '22 21:10

Pdroid