package de.theknut.xposedgelsettings.hooks.icon;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import de.theknut.xposedgelsettings.hooks.Common;
import de.theknut.xposedgelsettings.hooks.Utils;
import de.theknut.xposedgelsettings.ui.CommonUI;
public class IconPack {
static final String ICONBACK = "iconback";
static final String ICONMASK = "iconmask";
static final String ICONUPON = "iconupon";
static final int COMPONENTNAME = 0;
static final int ICONPACKNAME = 1;
static final int DRAWABLENAME = 2;
private List<IconInfo> appFilter;
private List<Icon> icons;
private List<IconInfo> calendarIcon;
private LinkedHashMap<String, List<IconPreview>> previewIcons;
private List<String> unthemedIcons;
private List<String> iconTheme;
private float scaleFactor = 0.75f;
private String packageName;
private Resources resources;
private Context context;
private Random rand;
private int launcherDPI;
private HashMap<String, Drawable> iconUponMask;
private List<Drawable> iconBack;
private Bitmap iconMask, iconUpon;
private Canvas mCanvas = new Canvas();
private Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
private Paint mMaskPaint = new Paint();
private static int dayOfMonth;
public IconPack(Context context, String packageName) throws NameNotFoundException {
this.packageName = packageName;
this.launcherDPI = context.getResources().getDisplayMetrics().densityDpi;
this.unthemedIcons = new ArrayList<String>();
this.icons = new ArrayList<Icon>();
this.calendarIcon = new ArrayList<IconInfo>();
setDayOfMonth(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
if (!packageName.equals(Common.ICONPACK_DEFAULT)) {
this.iconUponMask = new HashMap<String, Drawable>();
this.appFilter = new ArrayList<IconInfo>();
this.iconTheme = Arrays.asList(ICONBACK, ICONMASK, ICONUPON);
this.iconBack = new ArrayList<Drawable>();
this.context = context.createPackageContext(this.packageName, Context.CONTEXT_IGNORE_SECURITY);
this.resources = this.context.getResources();
this.rand = new Random();
} else {
this.context = context;
this.resources = this.context.getResources();
}
}
public Context getContext() {
return context;
}
public Resources getResources() {
return resources;
}
public List<Icon> getIcons() {
return icons;
}
public List<String> getUnthemedIcons() {
return unthemedIcons;
}
public List<IconInfo> getAppFilter() {
return appFilter;
}
public String getPackageName() {
return packageName;
}
public float getScaleFactor() {
return scaleFactor;
}
public void setScaleFactor(float scaleFactor) {
this.scaleFactor = scaleFactor;
}
public Drawable getIconMask() {
return this.iconUponMask.get(ICONMASK);
}
public Bitmap getIconMaskBitmap() {
if (iconMask == null) {
iconMask = CommonUI.drawableToBitmap(getIconMask());
}
return iconMask;
}
public boolean hasIconMask() {
return this.iconUponMask != null && this.iconUponMask.get(ICONMASK) != null;
}
public boolean hasIconBack() {
return this.iconBack != null && this.iconBack.size() != 0;
}
public boolean hasIconUpon() {
return this.iconUponMask != null && this.iconUponMask.get(ICONUPON) != null;
}
public Drawable getIconBack() {
return this.iconBack.get(this.rand.nextInt(this.iconBack.size()));
}
public Bitmap getIconBackBitmap() {
return CommonUI.drawableToBitmap(getIconBack());
}
public Bitmap getIconUponBitmap() {
if (iconUpon == null) {
iconUpon = CommonUI.drawableToBitmap(this.iconUponMask.get(ICONUPON));
}
return iconUpon;
}
public List<IconInfo> getCalendarIcons() {
return this.calendarIcon;
}
public int getDPI() {
return launcherDPI;
}
public static int getDayOfMonth() {
return IconPack.dayOfMonth;
}
public static void updateDayOfMonth() {
IconPack.dayOfMonth = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
}
public void setDayOfMonth(int dayOfMonth) {
IconPack.dayOfMonth = dayOfMonth;
}
public boolean shouldThemeMissingIcons() {
return hasIconBack() || hasIconMask() || hasIconUpon();
}
public boolean isAppFilterLoaded() {
return (appFilter != null && appFilter.size() != 0)
|| shouldThemeMissingIcons();
}
public void onDateChanged() {
IconPack.updateDayOfMonth();
List<ResolveInfo> calendars = IconHooks.getCalendars();
Iterator<Icon> it = icons.iterator();
while (it.hasNext()) {
Icon icon = it.next();
for (ResolveInfo calendar : calendars) {
if (icon.getPackageName().contains(calendar.activityInfo.packageName)) {
it.remove();
}
}
}
}
public Bitmap themeIcon(Bitmap tmpIcon) {
Bitmap tmpFinalIcon = Bitmap.createBitmap(tmpIcon.getWidth(), tmpIcon.getHeight(), Config.ARGB_8888);
drawFinalIcon(tmpFinalIcon, tmpIcon);
return tmpFinalIcon;
}
private void drawFinalIcon(Bitmap finalIcon, Bitmap originalIcon) {
Canvas c = new Canvas();
c.setBitmap(finalIcon);
if (hasIconBack()) {
Bitmap iconBack = Bitmap.createScaledBitmap(getIconBackBitmap(), originalIcon.getWidth(), originalIcon.getHeight(), true);
c.drawBitmap(iconBack, 0, 0, mPaint);
} else {
setScaleFactor(1.0f);
}
if (hasIconMask()) {
Bitmap iconMask = Bitmap.createScaledBitmap(getIconMaskBitmap(), originalIcon.getWidth(), originalIcon.getHeight(), true);
Bitmap tmpMask = Bitmap.createBitmap(iconMask.getWidth(), iconMask.getHeight(), Config.ARGB_8888);
applyMaskToBitmap(tmpMask, originalIcon, iconMask, getScaleFactor());
c.drawBitmap(tmpMask, 0, 0, mPaint);
} else {
applyScaleFactor(c, finalIcon, originalIcon, getScaleFactor());
}
if (hasIconUpon()) {
Bitmap iconUpon = Bitmap.createScaledBitmap(getIconUponBitmap(), originalIcon.getWidth(), originalIcon.getHeight(), true);
c.drawBitmap(iconUpon, 0, 0, mPaint);
}
c.setBitmap(null);
}
// Copyright Kevin Barry, TeslaCoil Software
private void applyMaskToBitmap(Bitmap dst, Bitmap src, Bitmap mask, float scale) {
if (dst != null) {
Canvas c = mCanvas;
c.setBitmap(dst);
applyScaleFactor(c, dst, src, scale);
c.drawBitmap(mask, 0, 0, mMaskPaint);
c.setBitmap(null);
}
}
// Copyright Kevin Barry, TeslaCoil Software
private void applyScaleFactor(Canvas c, Bitmap dst, Bitmap src, float scale) {
c.save();
c.translate(dst.getWidth()*.5f, dst.getHeight()*.5f);
c.scale(scale, scale);
int scaledW = dst.getScaledWidth(c);
int scaledH = dst.getScaledHeight(c);
int l = - scaledW/2;
int t = - scaledH/2;
c.drawBitmap(src, l, t, mPaint);
c.restore();
}
public Drawable loadIcon(String pkg) {
for (Icon icon : getIcons()) {
if (icon.equals(pkg)) {
return icon.getIcon();
}
}
if (isAppFilterLoaded()) {
int id = getResourceIdForDrawable(pkg);
if (id != 0) {
try {
Icon icon = new Icon(pkg, resources.getDrawableForDensity(id, getDPI()));
getIcons().add(icon);
return icon.getIcon();
} catch (Exception e) { }
}
}
unthemedIcons.add(pkg);
return null;
}
public void loadSelectedIcons(Set<String> selectedIcons) {
for (String selectedIcon : selectedIcons) {
String[] info = selectedIcon.split("\\|");
loadSingleIconFromIconPack(info[ICONPACKNAME], info[COMPONENTNAME], info[DRAWABLENAME]);
}
}
public Drawable loadSingleIconFromIconPack(String iconPackPackageName, String componentName, String drawableName) {
return loadSingleIconFromIconPack(iconPackPackageName, componentName, drawableName, true);
}
public Drawable loadSingleIconFromIconPack(String iconPackPackageName, String componentName, String drawableName, boolean addToCache) {
Resources res = getResources();
int id = 0;
if (isDefault(iconPackPackageName)) {
try {
iconPackPackageName = drawableName.substring(0, drawableName.indexOf(":"));
Context iconPackContext = getContext().createPackageContext(iconPackPackageName, Context.CONTEXT_IGNORE_SECURITY);
res = iconPackContext.getResources();
id = res.getIdentifier(drawableName.split("/")[1], drawableName.split("/")[0].split(":")[1], iconPackPackageName);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
} else if (iconPackPackageName.equals("sdcard")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& (ActivityCompat.checkSelfPermission(Common.LAUNCHER_CONTEXT, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(Common.LAUNCHER_CONTEXT, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
Utils.requestPermission(Common.LAUNCHER_INSTANCE, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, Common.PERMISSION_REQUEST_RESTART);
return null;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/XposedGELSettings/icons/" + drawableName + ".png", options);
if (bitmap == null) return null;
Drawable icon = new BitmapDrawable(res, bitmap);
if (addToCache) getIcons().add(new Icon(drawableName.replace("#", "/"), icon));
return icon;
} else {
if (!iconPackPackageName.equals(getPackageName())) {
try {
Context iconPackContext = getContext().createPackageContext(iconPackPackageName, Context.CONTEXT_IGNORE_SECURITY);
res = iconPackContext.getResources();
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
id = res.getIdentifier(drawableName, "drawable", iconPackPackageName);
}
if (id != 0) {
Drawable icon = res.getDrawableForDensity(id, getDPI());
if (addToCache) getIcons().add(new Icon(componentName, icon));
return icon;
}
return null;
}
public int getResourceIdForDrawable(String pkg) {
List<String> history = new ArrayList<String>();
int id = 0;
while (true) {
String drawableName = getDrawableName(pkg, history);
if (drawableName != "") {
id = resources.getIdentifier(drawableName, "drawable", getPackageName());
if (id == 0) {
history.add(drawableName);
continue;
}
return id;
} else {
break;
}
}
return 0;
}
public String getDrawableName(String pkg, List<String> history) {
String name = getIconNameExact(pkg, history);
if (name == "") {
name = getIconNameRelative(pkg, history);
}
return name;
}
public String getIconNameExact(String pkg, List<String> history) {
for (IconInfo icon : appFilter) {
if (icon.getComponentName().equals(pkg) && !(history.contains(icon.getDrawableName()))) {
return icon.getDrawableName();
}
}
return "";
}
public String getIconNameRelative(String pkg, List<String> history) {
for (IconInfo icon : appFilter) {
String iconName = pkg;
if (iconName.contains("/")) {
iconName = iconName.substring(0, iconName.indexOf('/'));
}
if (icon.getComponentName().contains(iconName) && !(history.contains(icon.getDrawableName()))) {
return icon.getDrawableName();
}
}
return "";
}
public int getTotalIconCount() {
List<String> names = new ArrayList<String>();
for (IconInfo icon : appFilter) {
if (!names.contains(icon.getDrawableName())) {
names.add(icon.getDrawableName());
}
}
return names.size();
}
public List<IconInfo> loadAppFilter() {
ArrayList<IconInfo> icons = new ArrayList<IconInfo>();
try {
Context resContext = getContext().createPackageContext(getPackageName(), Context.CONTEXT_IGNORE_SECURITY);
XmlPullParserFactory factory = null;
XmlPullParser parser = null;
Resources res = resContext.getResources();
int resId = res.getIdentifier("appfilter", "xml", packageName);
if (resId != 0) {
parser = res.getXml(resId);
} else {
factory = XmlPullParserFactory.newInstance();
parser = factory.newPullParser();
parser.setInput(new InputStreamReader(res.getAssets().open("appfilter.xml"), "UTF-8"));
}
for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT; eventType = parser.next()) {
String name = parser.getName();
if (name == null) continue;
if (name.equalsIgnoreCase("item")) {
readIconInfo(parser);
} else if (name.equalsIgnoreCase("calendar")) {
readCalendarInfo(parser);
} else if (iconTheme.contains(name.toLowerCase(Locale.US))) {
readAndLoadIconBackMaskUpon(name, res, parser);
} else if (name.equalsIgnoreCase("scale")) {
readScaleFactor(parser);
} else {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return icons;
}
private void readIconInfo(XmlPullParser parser) {
try {
parser.require(XmlPullParser.START_TAG, null, "item");
String drawableName = parser.getAttributeValue(null, "drawable");
if (TextUtils.isEmpty(drawableName)) {
drawableName = parser.getAttributeValue(null, "image");
}
String componentName = parser.getAttributeValue(null, "component");
if (componentName == null) {
componentName = parser.getAttributeValue(null, "Component");
}
componentName = componentName.replace("ComponentInfo{", "").replace("}", "");
if (!TextUtils.isEmpty(componentName) && !TextUtils.isEmpty(drawableName)) {
appFilter.add(new IconInfo(componentName, drawableName));
}
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "item");
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void readCalendarInfo(XmlPullParser parser) {
try {
parser.require(XmlPullParser.START_TAG, null, "calendar");
String componentName = parser.getAttributeValue(null, "component")
.replace("ComponentInfo{", "")
.replace("}", "");
String prefix = parser.getAttributeValue(null, "prefix");
IconInfo icon = new IconInfo(componentName, null, prefix);
appFilter.add(0, icon);
calendarIcon.add(icon);
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "calendar");
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void readScaleFactor(XmlPullParser parser) {
try {
parser.require(XmlPullParser.START_TAG, null, "scale");
setScaleFactor(Float.parseFloat(parser.getAttributeValue(0)));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "scale");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void readAndLoadIconBackMaskUpon(String name, Resources res, XmlPullParser parser) {
try {
if (name.equals(ICONBACK)) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
int resID = res.getIdentifier(parser.getAttributeValue(i), "drawable", getPackageName());
if (resID != 0) {
iconBack.add(res.getDrawable(resID));
}
}
} else {
int resID = res.getIdentifier(parser.getAttributeValue(0), "drawable", getPackageName());
if (resID != 0) {
iconUponMask.put(name, res.getDrawable(resID));
}
}
parser.nextTag();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void loadIconCategories(Context context) {
previewIcons = new LinkedHashMap<String, List<IconPreview>>();
ArrayList<IconPreview> icons = new ArrayList<IconPreview>();
String currentCategory = null;
if (isDefault()) {
loadDefaultCategories();
return;
}
try {
Context resContext = context.createPackageContext(getPackageName(), Context.CONTEXT_IGNORE_SECURITY);
XmlPullParserFactory factory = null;
XmlPullParser parser = null;
Resources res = resContext.getResources();
int resId = res.getIdentifier("drawable", "xml", getPackageName());
if (resId != 0) {
parser = res.getXml(resId);
} else {
factory = XmlPullParserFactory.newInstance();
parser = factory.newPullParser();
parser.setInput(new InputStreamReader(res.getAssets().open("drawable.xml"), "UTF-8"));
}
for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT; eventType = parser.next()) {
String name = parser.getName();
if (name == null) continue;
if (name.equalsIgnoreCase("item")) {
readPreviewIcon(parser, icons);
} else if (name.equalsIgnoreCase("category")) {
currentCategory = readCategory(parser, currentCategory, icons);
icons = new ArrayList<IconPreview>();
} else {
continue;
}
}
if (currentCategory == null) {
currentCategory = "all icons";
}
previewIcons.put(currentCategory, icons);
} catch (Exception e) {
loadAppFilter();
previewIcons.put("all icons", getIconPreviews(getAppFilter()));
}
}
private void loadDefaultCategories() {
ArrayList<IconPreview> icons = new ArrayList<IconPreview>();
try {
for (ResolveInfo info : CommonUI.getAllApps()) {
Resources res = getContext().createPackageContext(info.activityInfo.packageName, 0).getResources();
IconPreview icon = new IconPreview(info.getIconResource(), res.getResourceName(info.getIconResource()));
icon.setIcon(res.getDrawable(icon.getResID()));
icons.add(icon);
}
} catch (Exception e) {
e.printStackTrace();
}
previewIcons.put("all icons", icons);
}
private ArrayList<IconPreview> getIconPreviews(List<IconInfo> appFilter) {
ArrayList<IconPreview> result = new ArrayList<IconPreview>();
List<Integer> history = new ArrayList<Integer>();
for (IconInfo iconInfo : appFilter) {
int id = resources.getIdentifier(iconInfo.getDrawableName(), "drawable", getPackageName());
if (id != 0 && !history.contains(id)) {
history.add(id);
result.add(new IconPreview(id, iconInfo.getDrawableName()));
}
}
return result;
}
private String readCategory(XmlPullParser parser, String currentCategory, List<IconPreview> icons) {
try {
String name;
parser.require(XmlPullParser.START_TAG, null, "category");
if (currentCategory == null) {
name = parser.getAttributeValue(0);
parser.nextTag();
return name;
}
if (icons.size() != 0) {
previewIcons.put(currentCategory, icons);
}
name = parser.getAttributeValue(0);
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "category");
return name;
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private void readPreviewIcon(XmlPullParser parser, List<IconPreview> icons) {
try {
parser.require(XmlPullParser.START_TAG, null, "item");
String drawableName = parser.getAttributeValue(0);
int id = resources.getIdentifier(drawableName, "drawable", getPackageName());
if (id != 0) {
icons.add(new IconPreview(id, drawableName));
}
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "item");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public HashMap<String, List<IconPreview>> getIconPreviews() {
return previewIcons;
}
public boolean isDefault() {
return getPackageName().equals(Common.ICONPACK_DEFAULT);
}
public boolean isDefault(String packageName) {
return packageName.equals(Common.ICONPACK_DEFAULT);
}
}