Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 60723846 authored by Clark Scheff's avatar Clark Scheff Committed by Gerrit Code Review
Browse files

Merge "CM11 Themes: Add support for composed icons [1/2]" into cm-11.0

parents dd1df7b1 385e30d1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -372,6 +372,7 @@ aidl_files := \
	frameworks/base/core/java/android/accounts/IAccountManagerResponse.aidl \
	frameworks/base/core/java/android/accounts/IAccountAuthenticator.aidl \
	frameworks/base/core/java/android/accounts/IAccountAuthenticatorResponse.aidl \
	frameworks/base/core/java/android/app/ComposedIconInfo.aidl \
	frameworks/base/core/java/android/app/Notification.aidl \
	frameworks/base/core/java/android/app/NotificationGroup.aidl \
	frameworks/base/core/java/android/app/Profile.aidl \
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The CyanogenMod Project
 *
 * 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 android.app;

/** @hide */
parcelable ComposedIconInfo;
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The CyanogenMod Project
 *
 * 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 android.app;

import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Parcel;
import android.os.Parcelable;

/** @hide */
public class ComposedIconInfo implements Parcelable {
    public BitmapDrawable iconUpon, iconMask;
    public BitmapDrawable[] iconBacks;
    public float iconScale;
    public int iconDensity;
    public int iconSize;

    public ComposedIconInfo() {
        super();
    }

    private ComposedIconInfo(Parcel source) {
        ClassLoader bmpClassLoader = Bitmap.class.getClassLoader();
        iconScale = source.readFloat();
        iconDensity = source.readInt();
        iconSize = source.readInt();
        int backCount = source.readInt();
        if (backCount > 0) {
            iconBacks = new BitmapDrawable[backCount];
            for (int i = 0; i < backCount; i++) {
                iconBacks[i] = new BitmapDrawable((Bitmap) source.readParcelable(bmpClassLoader));
            }
        }
        iconMask = new BitmapDrawable((Bitmap) source.readParcelable(bmpClassLoader));
        iconUpon = new BitmapDrawable((Bitmap) source.readParcelable(bmpClassLoader));
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeFloat(iconScale);
        dest.writeInt(iconDensity);
        dest.writeInt(iconSize);
        dest.writeInt(iconBacks != null ? iconBacks.length : 0);
        if (iconBacks != null) {
            for (BitmapDrawable d : iconBacks) {
                dest.writeParcelable(d != null ? d.getBitmap() : null, flags);
            }
        }
        dest.writeParcelable(iconMask != null ? iconMask.getBitmap() : null, flags);
        dest.writeParcelable(iconUpon != null ? iconUpon.getBitmap() : null, flags);
    }

    public static final Creator<ComposedIconInfo> CREATOR
            = new Creator<ComposedIconInfo>() {
        @Override
        public ComposedIconInfo createFromParcel(Parcel source) {
            return new ComposedIconInfo(source);
        }

        @Override
        public ComposedIconInfo[] newArray(int size) {
            return new ComposedIconInfo[0];
        }
    };
}
+284 −20
Original line number Diff line number Diff line
@@ -15,12 +15,28 @@
 */
package android.app;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import android.content.pm.PackageInfo;
import android.content.res.IThemeService;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.PaintDrawable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.TypedValue;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
@@ -40,24 +56,47 @@ import android.util.DisplayMetrics;

/** @hide */
public class IconPackHelper {
    private static final String TAG = IconPackHelper.class.getSimpleName();
    private static final String ICON_MASK_TAG = "iconmask";
    private static final String ICON_BACK_TAG = "iconback";
    private static final String ICON_UPON_TAG = "iconupon";
    private static final String ICON_SCALE_TAG = "scale";
    private static final String ICON_BACK_FORMAT = "iconback%d";

    private static final ComponentName ICON_BACK_COMPONENT;
    private static final ComponentName ICON_MASK_COMPONENT;
    private static final ComponentName ICON_UPON_COMPONENT;
    private static final ComponentName ICON_SCALE_COMPONENT;

    private static final float DEFAULT_SCALE = 1.0f;
    private static final int COMPOSED_ICON_COOKIE = 128;

    private final Context mContext;
    private Map<ComponentName, String> mIconPackResourceMap;
    private String mLoadedIconPackName;
    private Resources mLoadedIconPackResource;
    private float mIconScale;
    private ComposedIconInfo mComposedIconInfo;
    private int mIconBackCount = 0;

    static {
        ICON_BACK_COMPONENT = new ComponentName(ICON_BACK_TAG, "");
        ICON_MASK_COMPONENT = new ComponentName(ICON_MASK_TAG, "");
        ICON_UPON_COMPONENT = new ComponentName(ICON_UPON_TAG, "");
        ICON_SCALE_COMPONENT = new ComponentName(ICON_SCALE_TAG, "");
    }

    public IconPackHelper(Context context) {
        mContext = context;
        mIconPackResourceMap = new HashMap<ComponentName, String>();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        mComposedIconInfo = new ComposedIconInfo();
        mComposedIconInfo.iconSize = am.getLauncherLargeIconSize();
        mComposedIconInfo.iconDensity = am.getLauncherLargeIconDensity();
    }

    private static void loadResourcesFromXmlParser(XmlPullParser parser,
    private void loadResourcesFromXmlParser(XmlPullParser parser,
            Map<ComponentName, String> iconPackResources) throws XmlPullParserException, IOException {
        mIconBackCount = 0;
        int eventType = parser.getEventType();
        do {

@@ -65,6 +104,21 @@ public class IconPackHelper {
                continue;
            }

            if (parseComposedIconComponent(parser, iconPackResources)) {
                continue;
            }

            if (parser.getName().equalsIgnoreCase(ICON_SCALE_TAG)) {
                String factor = parser.getAttributeValue(null, "factor");
                if (factor == null) {
                    if (parser.getAttributeCount() == 1) {
                        factor = parser.getAttributeValue(0);
                    }
                }
                iconPackResources.put(ICON_SCALE_COMPONENT, factor);
                continue;
            }

            if (!parser.getName().equalsIgnoreCase("item")) {
                continue;
            }
@@ -100,28 +154,98 @@ public class IconPackHelper {
        } while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT);
    }

    private boolean isComposedIconComponent(String tag) {
        return tag.equalsIgnoreCase(ICON_MASK_TAG) ||
                tag.equalsIgnoreCase(ICON_BACK_TAG) ||
                tag.equalsIgnoreCase(ICON_UPON_TAG);
    }

    private boolean parseComposedIconComponent(XmlPullParser parser,
                                            Map<ComponentName, String> iconPackResources) {
        String icon;
        String tag = parser.getName();
        if (!isComposedIconComponent(tag)) {
            return false;
        }

        if (parser.getAttributeCount() >= 1) {
            if (tag.equalsIgnoreCase(ICON_BACK_TAG)) {
                mIconBackCount = parser.getAttributeCount();
                for (int i = 0; i < mIconBackCount; i++) {
                    tag = String.format(ICON_BACK_FORMAT, i);
                    icon = parser.getAttributeValue(i);
                    iconPackResources.put(new ComponentName(tag, ""), icon);
                }
            } else {
                icon = parser.getAttributeValue(0);
                iconPackResources.put(new ComponentName(tag, ""),
                        icon);
            }
            return true;
        }

        return false;
    }

    public void loadIconPack(String packageName) throws NameNotFoundException {
        if (packageName == null) {
            mLoadedIconPackResource = null;
            mLoadedIconPackName = null;
            mComposedIconInfo.iconBacks = null;
            mComposedIconInfo.iconMask = mComposedIconInfo.iconUpon = null;
            mComposedIconInfo.iconScale = 0;
        } else {
            mIconBackCount = 0;
            Resources res = createIconResource(mContext, packageName);
            mIconPackResourceMap = getIconResMapFromXml(res, packageName);
            mLoadedIconPackResource = res;
            mLoadedIconPackName = packageName;
            String scale = mIconPackResourceMap.get(ICON_SCALE_TAG);
            loadComposedIconComponents();
        }
    }

    public ComposedIconInfo getComposedIconInfo() {
        return mComposedIconInfo;
    }

    private void loadComposedIconComponents() {
        mComposedIconInfo.iconMask = (BitmapDrawable) getDrawableForName(ICON_MASK_COMPONENT);
        mComposedIconInfo.iconUpon = (BitmapDrawable) getDrawableForName(ICON_UPON_COMPONENT);

        // Take care of loading iconback which can have multiple images
        if (mIconBackCount > 0) {
            mComposedIconInfo.iconBacks = new BitmapDrawable[mIconBackCount];
            for (int i = 0; i < mIconBackCount; i++) {
                mComposedIconInfo.iconBacks[i] =
                        (BitmapDrawable) getDrawableForName(
                                new ComponentName(String.format(ICON_BACK_FORMAT, i), ""));
            }
        }

        // Get the icon scale from this pack
        String scale = mIconPackResourceMap.get(ICON_SCALE_COMPONENT);
        if (scale != null) {
            try {
                    mIconScale = Float.valueOf(scale);
                mComposedIconInfo.iconScale = Float.valueOf(scale);
            } catch (NumberFormatException e) {
                mComposedIconInfo.iconScale = DEFAULT_SCALE;
            }
        } else {
            mComposedIconInfo.iconScale = DEFAULT_SCALE;
        }
    }

    private Drawable getDrawableForName(ComponentName component) {
        if (isIconPackLoaded()) {
            String item = mIconPackResourceMap.get(component);
            if (!TextUtils.isEmpty(item)) {
                int id = getResourceIdForDrawable(item);
                if (id != 0) {
                    return mLoadedIconPackResource.getDrawable(id);
                }
            }

    public float getIconScale() {
        return mIconScale;
        }
        return null;
    }

    public static Resources createIconResource(Context context, String packageName) throws NameNotFoundException {
@@ -151,7 +275,7 @@ public class IconPackHelper {
        return res;
    }

    public static Map<ComponentName, String> getIconResMapFromXml(Resources res, String packageName) {
    public Map<ComponentName, String> getIconResMapFromXml(Resources res, String packageName) {
        XmlPullParser parser = null;
        InputStream inputStream = null;
        Map<ComponentName, String> iconPackResources = new HashMap<ComponentName, String>();
@@ -254,16 +378,20 @@ public class IconPackHelper {
        if (!isIconPackLoaded()) {
            return 0;
        }
        ComponentName compName = new ComponentName(info.packageName.toLowerCase(), info.name.toLowerCase());
        ComponentName compName = new ComponentName(info.packageName.toLowerCase(),
                info.name.toLowerCase());
        String drawable = mIconPackResourceMap.get(compName);
        if (drawable == null) {
        if (drawable != null) {
            int resId = getResourceIdForDrawable(drawable);
            if (resId != 0) return resId;
        }

        // Icon pack doesn't have an icon for the activity, fallback to package icon
        compName = new ComponentName(info.packageName.toLowerCase(), "");
        drawable = mIconPackResourceMap.get(compName);
        if (drawable == null) {
            return 0;
        }
        }
        return getResourceIdForDrawable(drawable);
    }

@@ -277,12 +405,148 @@ public class IconPackHelper {
    public Drawable getDrawableForActivity(ActivityInfo info) {
        int id = getResourceIdForActivityIcon(info);
        if (id == 0) return null;
        return mLoadedIconPackResource.getDrawable(id);
        return mLoadedIconPackResource.getDrawable(id, false);
    }

    public Drawable getDrawableForActivityWithDensity(ActivityInfo info, int density) {
        int id = getResourceIdForActivityIcon(info);
        if (id == 0) return null;
        return mLoadedIconPackResource.getDrawableForDensity(id, density);
        return mLoadedIconPackResource.getDrawableForDensity(id, density, false);
    }

    public static class IconCustomizer {
        private static final Random sRandom = new Random();
        private static final IThemeService sThemeService;

        static {
            sThemeService = IThemeService.Stub.asInterface(
                    ServiceManager.getService(Context.THEME_SERVICE));
        }

        public static Drawable getComposedIconDrawable(Drawable icon, Context context,
                                                       ComposedIconInfo iconInfo) {
            final Resources res = context.getResources();
            return getComposedIconDrawable(icon, res, iconInfo);
        }

        public static Drawable getComposedIconDrawable(Drawable icon, Resources res,
                                                       ComposedIconInfo iconInfo) {
            if (iconInfo == null) return icon;
            Drawable back = null;
            if (iconInfo.iconBacks != null && iconInfo.iconBacks.length > 0) {
                back = iconInfo.iconBacks[sRandom.nextInt(iconInfo.iconBacks.length)];
            }
            Bitmap bmp = createIconBitmap(icon, res, back, iconInfo.iconMask, iconInfo.iconUpon,
                    iconInfo.iconScale, iconInfo.iconSize);
            return bmp != null ? new BitmapDrawable(res, bmp): null;
        }

        public static void getValue(Resources res, int resId, TypedValue outValue,
                                    Drawable baseIcon) {
            if (!(baseIcon instanceof BitmapDrawable)) return;

            final String pkgName = res.getAssets().getAppName();
            TypedValue tempValue = new TypedValue();
            tempValue.setTo(outValue);
            outValue.assetCookie = COMPOSED_ICON_COOKIE;
            outValue.data = resId & (COMPOSED_ICON_COOKIE << 24 | 0x00ffffff);
            outValue.string = getCachedIconPath(pkgName, resId, outValue.density);

            if (!(new File(outValue.string.toString()).exists())) {
                // compose the icon and cache it
                final ComposedIconInfo iconInfo = res.getComposedIconInfo();
                Drawable back = null;
                if (iconInfo.iconBacks != null && iconInfo.iconBacks.length > 0) {
                    back = iconInfo.iconBacks[(outValue.string.hashCode() & 0x7fffffff)
                            % iconInfo.iconBacks.length];
                }
                Bitmap bmp = createIconBitmap(baseIcon, res, back, iconInfo.iconMask,
                        iconInfo.iconUpon, iconInfo.iconScale, iconInfo.iconSize);
                if (!cacheComposedIcon(bmp, getCachedIconName(pkgName, resId, outValue.density))) {
                    Log.w(TAG, "Unable to cache icon " + outValue.string);
                    // restore the original TypedValue
                    outValue.setTo(tempValue);
                }
            }
        }

        private static Bitmap createIconBitmap(Drawable icon, Resources res, Drawable iconBack,
                                               Drawable iconMask, Drawable iconUpon, float scale,
                                               int iconSize) {
            if (iconSize <= 0) return null;

            final Canvas canvas = new Canvas();
            canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG,
                    Paint.FILTER_BITMAP_FLAG));

            if (icon instanceof PaintDrawable) {
                PaintDrawable painter = (PaintDrawable) icon;
                painter.setIntrinsicWidth(iconSize);
                painter.setIntrinsicHeight(iconSize);
            } else if (icon instanceof BitmapDrawable) {
                // Ensure the bitmap has a density.
                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                Bitmap bitmap = bitmapDrawable.getBitmap();
                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
                    bitmapDrawable.setTargetDensity(res.getDisplayMetrics());
                }
                canvas.setDensity(bitmap.getDensity());
            }

            Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize,
                    Bitmap.Config.ARGB_8888);
            canvas.setBitmap(bitmap);

            // Scale the original
            Rect oldBounds = new Rect();
            oldBounds.set(icon.getBounds());
            icon.setBounds(0, 0, iconSize, iconSize);
            canvas.save();
            canvas.scale(scale, scale, iconSize / 2, iconSize / 2);
            icon.draw(canvas);
            canvas.restore();

            // Mask off the original if iconMask is not null
            if (iconMask != null) {
                iconMask.setBounds(icon.getBounds());
                ((BitmapDrawable) iconMask).getPaint().setXfermode(
                        new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
                iconMask.draw(canvas);
            }
            // Draw the iconBacks if not null and then the original (scaled and masked) icon on top
            if (iconBack != null) {
                iconBack.setBounds(icon.getBounds());
                ((BitmapDrawable) iconBack).getPaint().setXfermode(
                        new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
                iconBack.draw(canvas);
            }
            // Finally draw the foreground if one was supplied
            if (iconUpon != null) {
                iconUpon.draw(canvas);
            }
            icon.setBounds(oldBounds);
            bitmap.setDensity(canvas.getDensity());

            return bitmap;
        }

        private static boolean cacheComposedIcon(Bitmap bmp, String path) {
            try {
                return sThemeService.cacheComposedIcon(bmp, path);
            } catch (RemoteException e) {
                Log.e(TAG, "Unable to cache icon.", e);
            }

            return false;
        }

        private static String getCachedIconPath(String pkgName, int resId, int density) {
            return String.format("%s/%s", ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR,
                    getCachedIconName(pkgName, resId, density));
        }

        private static String getCachedIconName(String pkgName, int resId, int density) {
            return String.format("%s_%08x_%d.png", pkgName, resId, density);
        }
    }
}
+15 −3
Original line number Diff line number Diff line
@@ -284,20 +284,31 @@ public class ResourcesManager {
        //Map application icon
        if (pkgInfo != null && pkgInfo.applicationInfo != null) {
            appInfo = pkgInfo.applicationInfo;
            if (appInfo.themedIcon != 0) iconResources.put(appInfo.icon, appInfo);
            if (appInfo.themedIcon != 0 || iconResources.get(appInfo.icon) == null) {
                iconResources.put(appInfo.icon, appInfo);
            }
        }

        //Map activity icons.
        if (pkgInfo != null && pkgInfo.activities != null) {
            for (ActivityInfo ai : pkgInfo.activities) {
                if (ai.themedIcon != 0 && ai.icon != 0) {
                if (ai.icon != 0 && (ai.themedIcon != 0 || iconResources.get(ai.icon) == null)) {
                    iconResources.put(ai.icon, ai);
                } else if (ai.themedIcon != 0 && appInfo != null && appInfo.icon != 0) {
                } else if (appInfo != null && appInfo.icon != 0 &&
                        (ai.themedIcon != 0 || iconResources.get(appInfo.icon) == null)) {
                    iconResources.put(appInfo.icon, ai);
                }
            }
        }

        r.setIconResources(iconResources);
        final IPackageManager pm = getPackageManager();
        try {
            ComposedIconInfo iconInfo = pm.getComposedIconInfo();
            r.setComposedIconInfo(iconInfo);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public final int applyConfigurationToResourcesLocked(Configuration config,
@@ -349,6 +360,7 @@ public class ResourcesManager {
                    AssetManager am = r.getAssets();
                    if (am.hasThemeSupport()) {
                        r.setIconResources(null);
                        r.setComposedIconInfo(null);
                        detachThemeAssets(am);
                        if (config.customTheme != null) {
                            attachThemeAssets(am, config.customTheme);
Loading