/* * Copyright 2014 Klinker Apps Inc. * * 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 xyz.klinker.blur.addons.utils; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.*; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.TextView; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.preference.PreferenceManager; import android.text.TextUtils; import xyz.klinker.blur.R; import xyz.klinker.blur.addons.settings.AppSettings; import xyz.klinker.blur.launcher3.IconCache; import xyz.klinker.blur.launcher3.InvariantDeviceProfile; public class IconPackHelper { static final String ICON_MASK_TAG = "iconmask"; static final String ICON_BACK_TAG = "iconback"; static final String ICON_UPON_TAG = "iconupon"; static final String ICON_SCALE_TAG = "scale"; public final static String[] sSupportedActions = new String[] { "org.adw.launcher.THEMES", "com.gau.go.launcherex.theme" }; public static final String[] sSupportedCategories = new String[] { "com.fede.launcher.THEME_ICONPACK", "com.anddoes.launcher.THEME", "com.teslacoilsw.launcher.THEME" }; // Holds package/class -> drawable private Map<String, String> mIconPackResources; private final Context mContext; private String mLoadedIconPackName; private Resources mLoadedIconPackResource; private Drawable[] mIconBack; private Drawable mIconUpon, mIconMask; private Drawable mAllApps; private float mIconScale; public Context getContext() { return mContext; } public Drawable getIconBack() { int index = randInt(0, mIconBack.length - 1); try { Drawable back = mIconBack[index]; return back; } catch (ArrayIndexOutOfBoundsException e) { if (mIconBack.length > 0) { return mIconBack[0]; } else { return null; } } } public Drawable getIconMask() { return mIconMask; } public Drawable getIconUpon() { return mIconUpon; } public float getIconScale() { return mIconScale; } public Drawable getAllAppsButton() { return mAllApps; } public IconPackHelper(Context context) { mContext = context; mIconPackResources = new HashMap<String, String>(); sharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); customIcons = sharedPrefs.getStringSet("custom_icons", new HashSet<String>()); } private Drawable getDrawableForName(String name) { if (isIconPackLoaded()) { String item = mIconPackResources.get(name); if (!TextUtils.isEmpty(item)) { int id = getResourceIdForDrawable(item); if (id != 0) { return mLoadedIconPackResource.getDrawable(id); } } } return null; } public static Map<String, IconPackInfo> getSupportedPackages(Context context) { Intent i = new Intent(); Map<String, IconPackInfo> packages = new HashMap<String, IconPackInfo>(); PackageManager packageManager = context.getPackageManager(); for (String action : sSupportedActions) { i.setAction(action); for (ResolveInfo r : packageManager.queryIntentActivities(i, 0)) { IconPackInfo info = new IconPackInfo(r, packageManager); packages.put(r.activityInfo.packageName, info); } } i = new Intent(Intent.ACTION_MAIN); for (String category : sSupportedCategories) { i.addCategory(category); for (ResolveInfo r : packageManager.queryIntentActivities(i, 0)) { IconPackInfo info = new IconPackInfo(r, packageManager); packages.put(r.activityInfo.packageName, info); } i.removeCategory(category); } return packages; } private static void loadResourcesFromXmlParser(XmlPullParser parser, Map<String, String> iconPackResources) throws XmlPullParserException, IOException { int eventType = parser.getEventType(); iconPackResources.put("number_backs", "0"); do { if (eventType != XmlPullParser.START_TAG) { continue; } if (parser.getName().equalsIgnoreCase(ICON_UPON_TAG)) { String icon = parser.getAttributeValue(null, "img"); if (icon == null) { if (parser.getAttributeCount() == 1) { icon = parser.getAttributeValue(0); } } iconPackResources.put(parser.getName().toLowerCase(), icon); continue; } if (parser.getName().equalsIgnoreCase(ICON_MASK_TAG)) { String icon = parser.getAttributeValue(null, "img"); if (icon == null) { if (parser.getAttributeCount() == 1) { icon = parser.getAttributeValue(0); } } iconPackResources.put(parser.getName().toLowerCase(), icon); continue; } if (parser.getName().equalsIgnoreCase(ICON_BACK_TAG)) { int number = parser.getAttributeCount(); iconPackResources.put("number_backs", "" + number); for (int i = 1; i < number; i++) { String icon = parser.getAttributeValue(null, "img" + i); if (icon == null) { if (parser.getAttributeCount() == 1) { icon = parser.getAttributeValue(0); } } Log.v("launcher_icons", "" + icon); iconPackResources.put(parser.getName().toLowerCase() + "_" + i, icon); } 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(parser.getName().toLowerCase(), factor); continue; } if (!parser.getName().equalsIgnoreCase("item")) { continue; } String component = parser.getAttributeValue(null, "component"); String drawable = parser.getAttributeValue(null, "drawable"); //Log.v("icon_pack", component); // Validate component/drawable exist if (TextUtils.isEmpty(component) || TextUtils.isEmpty(drawable)) { continue; } // Validate format/length of component if (!component.startsWith("ComponentInfo{") || !component.endsWith("}") || component.length() < 16) { continue; } // Sanitize stored value component = component.substring(14, component.length() - 1).toLowerCase(); ComponentName name = null; if (!component.contains("/")) { // Package icon reference iconPackResources.put(component, drawable); } else { name = ComponentName.unflattenFromString(component); if (name != null) { iconPackResources.put(name.getPackageName(), drawable); iconPackResources.put(name.getPackageName() + "." + name.getClassName(), drawable); } } } while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT); } /** * Returns a pseudo-random number between min and max, inclusive. * The difference between min and max can be at most * <code>Integer.MAX_VALUE - 1</code>. * * @param min Minimum value * @param max Maximum value. Must be greater than min. * @return Integer between min and max, inclusive. * @see java.util.Random#nextInt(int) */ public static int randInt(int min, int max) { // Usually this can be a field rather than a method variable Random rand = new Random(); // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive try { int randomNum = rand.nextInt((max - min) + 1) + min; return randomNum; } catch (Exception e) { return min; } } private static void loadApplicationResources(Context context, Map<String, String> iconPackResources, String packageName) { Field[] drawableItems = null; try { Context appContext = context.createPackageContext(packageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); drawableItems = Class.forName(packageName+".R$drawable", true, appContext.getClassLoader()).getFields(); } catch (Exception e){ return; } for (Field f : drawableItems) { String name = f.getName(); String icon = name.toLowerCase(); name = name.replaceAll("_", "."); iconPackResources.put(name, icon); int activityIndex = name.lastIndexOf("."); if (activityIndex <= 0 || activityIndex == name.length() - 1) { continue; } String iconPackage = name.substring(0, activityIndex); if (TextUtils.isEmpty(iconPackage)) { continue; } iconPackResources.put(iconPackage, icon); String iconActivity = name.substring(activityIndex + 1); if (TextUtils.isEmpty(iconActivity)) { continue; } iconPackResources.put(iconPackage + "." + iconActivity, icon); } } public boolean loadIconPack(String packageName) { mIconPackResources = getIconPackResources(mContext, packageName); Resources res = null; try { res = mContext.getPackageManager().getResourcesForApplication(packageName); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return false; } mLoadedIconPackResource = res; mLoadedIconPackName = packageName; try { int appButton = mLoadedIconPackResource.getIdentifier("all_apps_button", "drawable", AppSettings.getInstance(mContext).iconPack); mAllApps = mLoadedIconPackResource.getDrawable(appButton); } catch (Exception e) { mAllApps = mContext.getResources().getDrawable(getAllAppsStyledButton(mContext)); } int numberBacks = Integer.parseInt(mIconPackResources.get("number_backs")); if (numberBacks > 1) { numberBacks--; } Log.v("launcher_icons", "number backs = " + numberBacks); mIconBack = new Drawable[numberBacks]; for (int i = 0; i < numberBacks; i++) { mIconBack[i] = getDrawableForName(ICON_BACK_TAG + "_" + (i+1)); } mIconMask = getDrawableForName(ICON_MASK_TAG); mIconUpon = getDrawableForName(ICON_UPON_TAG); String scale = mIconPackResources.get(ICON_SCALE_TAG); if (scale != null) { try { mIconScale = Float.valueOf(scale); } catch (NumberFormatException e) { } } return true; } public static Map<String, String> getIconPackResources(Context context, String packageName) { if (TextUtils.isEmpty(packageName)) { return null; } Resources res = null; try { res = context.getPackageManager().getResourcesForApplication(packageName); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return null; } XmlPullParser parser = null; InputStream inputStream = null; Map<String, String> iconPackResources = new HashMap<String, String>(); try { inputStream = res.getAssets().open("appfilter.xml"); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); parser = factory.newPullParser(); parser.setInput(inputStream, "UTF-8"); } catch (Exception e) { // Catch any exception since we want to fall back to parsing the xml/ // resource in all cases int resId = res.getIdentifier("appfilter", "xml", packageName); if (resId != 0) { parser = res.getXml(resId); } } if (parser != null) { try { loadResourcesFromXmlParser(parser, iconPackResources); return iconPackResources; } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // Cleanup resources if (parser instanceof XmlResourceParser) { ((XmlResourceParser) parser).close(); } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } } // Application uses a different theme format (most likely launcher pro) int arrayId = res.getIdentifier("theme_iconpack", "array", packageName); if (arrayId == 0) { arrayId = res.getIdentifier("icon_pack", "array", packageName); } if (arrayId != 0) { String[] iconPack = res.getStringArray(arrayId); for (String entry : iconPack) { if (TextUtils.isEmpty(entry)) { continue; } String icon = entry.toLowerCase(); entry = entry.replaceAll("_", "."); iconPackResources.put(entry, icon); int activityIndex = entry.lastIndexOf("."); if (activityIndex <= 0 || activityIndex == entry.length() - 1) { continue; } String iconPackage = entry.substring(0, activityIndex); if (TextUtils.isEmpty(iconPackage)) { continue; } iconPackResources.put(iconPackage, icon); String iconActivity = entry.substring(activityIndex + 1); if (TextUtils.isEmpty(iconActivity)) { continue; } iconPackResources.put(iconPackage + "." + iconActivity, icon); } } else { loadApplicationResources(context, iconPackResources, packageName); } return iconPackResources; } public void unloadIconPack() { mLoadedIconPackResource = null; mLoadedIconPackName = null; mIconPackResources = null; mIconMask = null; mIconBack = null; mIconUpon = null; mIconScale = 1f; } public boolean isIconPackLoaded() { return mLoadedIconPackResource != null && mLoadedIconPackName != null && mIconPackResources != null; } private int getResourceIdForDrawable(String resource) { int resId = mLoadedIconPackResource.getIdentifier(resource, "drawable", mLoadedIconPackName); return resId; } public Resources getIconPackResources() { return mLoadedIconPackResource; } private SharedPreferences sharedPrefs = null; private Set<String> customIcons = null; public int getResourceIdForActivityIcon(ActivityInfo info) { return getResourceIdForActivityIcon(info.packageName, info.name.toLowerCase()); } public int getResourceIdForActivityIcon(String packageName, String component) { String string = component.toLowerCase(); String s = ""; for (String icon : customIcons) { s += icon + ", "; } Log.v("icon_helper", "custom icons: " + s); if (customIcons.contains(string)) { return getResourceIdForDrawable(sharedPrefs.getString(string, "")); } else { String drawable = mIconPackResources.get(string); if (drawable == null) { // Icon pack doesn't have an icon for the activity, fallback to package icon drawable = mIconPackResources.get(packageName.toLowerCase()); if (drawable == null) { return 0; } } return getResourceIdForDrawable(drawable); } } static class IconPackInfo { String packageName; CharSequence label; Drawable icon; IconPackInfo(ResolveInfo r, PackageManager packageManager) { packageName = r.activityInfo.packageName; icon = r.loadIcon(packageManager); label = r.loadLabel(packageManager); Log.v("klinker_launcher", "label: " + label); } IconPackInfo(){ } public IconPackInfo(String label, Drawable icon, String packageName) { this.label = label; this.icon = icon; this.packageName = packageName; } } private static class IconPack { public CharSequence packageName; public String text; public int icon; public Drawable actualIcon; public IconPack(String text, Drawable icon, CharSequence packageName) { this.text = text; this.actualIcon = icon; this.packageName = packageName; } } public static void pickIconPack(final Context context) { final PackageManager packageManager = context.getPackageManager(); final Map<String, IconPackInfo> supportedPackages = getSupportedPackages(context); AppSettings settings = AppSettings.getInstance(context); final CharSequence[] dialogEntries = new CharSequence[supportedPackages.size() + 1]; supportedPackages.keySet().toArray(dialogEntries); final String defaultIcons = context.getResources().getString(R.string.default_iconpack_title); dialogEntries[dialogEntries.length - 1] = defaultIcons; Arrays.sort(dialogEntries); final IconPack[] iconPacks = new IconPack[dialogEntries.length]; for (int i = 0; i < iconPacks.length; i++) { try { CharSequence entry = dialogEntries[i]; if (entry.equals(defaultIcons)) { iconPacks[i] = new IconPack(context.getResources().getString(R.string.default_iconpack_title), context.getResources().getDrawable(R.mipmap.ic_launcher_home), defaultIcons); } else { CharSequence pack = supportedPackages.get(entry).packageName; iconPacks[i] = new IconPack(supportedPackages.get(entry).label.toString(), packageManager.getApplicationIcon(packageManager.getApplicationInfo(pack.toString(), 0)), pack); } } catch (Exception e) { e.printStackTrace(); } } Arrays.sort(iconPacks, new Comparator<IconPack>() { @Override public int compare(IconPack lhs, IconPack rhs) { if (lhs.text.compareTo(rhs.text) < 0) { return -1; } else { return 1; } } }); ListAdapter adapter = new ArrayAdapter<IconPack>( context, R.layout.pack_picker_item, R.id.label, iconPacks) { public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); ImageView iv = (ImageView) v.findViewById(R.id.icon); TextView tv = (TextView) v.findViewById(R.id.label); iv.setImageDrawable(iconPacks[position].actualIcon); tv.setText(iconPacks[position].text); return v; } }; AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.dialog_pick_iconpack_title); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { CharSequence selectedPackage = iconPacks[which].packageName; if (selectedPackage.equals(defaultIcons)) { PreferenceManager.getDefaultSharedPreferences(context).edit() .putString("icon_pack", "") .commit(); } else { PreferenceManager.getDefaultSharedPreferences(context).edit() .putString("icon_pack", supportedPackages.get(selectedPackage).packageName) .commit(); } IconCache cache = new IconCache(context, new InvariantDeviceProfile()); cache.updateDbIcons(new HashSet<String>()); dialog.dismiss(); } }); builder.show(); } public static int getAllAppsStyledButton(Context context) { int allAppsDrawable = R.drawable.ic_allapps; return allAppsDrawable; } }