summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/icons/IconsHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/icons/IconsHandler.java')
-rw-r--r--src/com/android/launcher3/icons/IconsHandler.java620
1 files changed, 620 insertions, 0 deletions
diff --git a/src/com/android/launcher3/icons/IconsHandler.java b/src/com/android/launcher3/icons/IconsHandler.java
new file mode 100644
index 000000000..3a923dac9
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconsHandler.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2017 The LineageOS Project
+ * Copyright (C) 2017 Paranoid Android
+ *
+ * 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 com.android.launcher3.icons;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+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.AsyncTask;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.SettingsActivity;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherIcons;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+public class IconsHandler {
+ private static final String TAG = "IconsHandler";
+ private static String[] LAUNCHER_INTENTS = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.teslacoilsw.launcher.THEME",
+ "com.gau.go.launcherex.theme",
+ "org.adw.launcher.THEMES",
+ "org.adw.launcher.icons.ACTION_PICK_ICON"
+ };
+
+ private Map<String, IconPackInfo> mIconPacks = new HashMap<>();
+ private Map<String, String> mAppFilterDrawables = new HashMap<>();
+ private List<Bitmap> mBackImages = new ArrayList<>();
+ private List<String> mDrawables = new ArrayList<>();
+
+ private Bitmap mFrontImage;
+ private Bitmap mMaskImage;
+ private Bitmap mTmpBitmap;
+
+ private Resources mCurrentIconPackRes;
+ private Resources mOriginalIconPackRes;
+ private String mIconPackPackageName;
+
+ private AlertDialog mAlertDialog;
+ private Context mContext;
+ private IconCache mIconCache;
+ private PackageManager mPackageManager;
+ private String mDefaultIconPack;
+
+ private boolean mDialogShowing;
+ private float mFactor = 1.0f;
+
+ public IconsHandler(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mDefaultIconPack = context.getString(R.string.icon_pack_default);
+
+ SharedPreferences prefs = Utilities.getPrefs(context.getApplicationContext());
+ String iconPack = prefs.getString(SettingsActivity.KEY_ICON_PACK, mDefaultIconPack);
+ loadAvailableIconPacks();
+ loadIconPack(iconPack, false);
+ }
+
+ private void loadIconPack(String packageName, boolean fallback) {
+ mIconPackPackageName = packageName;
+ if (!fallback) {
+ mAppFilterDrawables.clear();
+ mBackImages.clear();
+ clearCache();
+ } else {
+ mDrawables.clear();
+ }
+
+ if (isDefaultIconPack()) {
+ return;
+ }
+
+ XmlPullParser xpp = null;
+
+ try {
+ mOriginalIconPackRes = mPackageManager.getResourcesForApplication(mIconPackPackageName);
+ mCurrentIconPackRes = mOriginalIconPackRes;
+ int appfilterid = mOriginalIconPackRes.getIdentifier("appfilter", "xml",
+ mIconPackPackageName);
+ if (appfilterid > 0) {
+ xpp = mOriginalIconPackRes.getXml(appfilterid);
+ }
+
+ if (xpp != null) {
+ int eventType = xpp.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (!fallback & xpp.getName().equals("iconback")) {
+ for (int i = 0; i < xpp.getAttributeCount(); i++) {
+ if (xpp.getAttributeName(i).startsWith("img")) {
+ String drawableName = xpp.getAttributeValue(i);
+ Bitmap iconback = loadBitmap(drawableName);
+ if (iconback != null) {
+ mBackImages.add(iconback);
+ }
+ }
+ }
+ } else if (!fallback && xpp.getName().equals("iconmask")) {
+ if (xpp.getAttributeCount() > 0 &&
+ xpp.getAttributeName(0).equals("img1")) {
+ String drawableName = xpp.getAttributeValue(0);
+ mMaskImage = loadBitmap(drawableName);
+ }
+ } else if (!fallback && xpp.getName().equals("iconupon")) {
+ if (xpp.getAttributeCount() > 0 &&
+ xpp.getAttributeName(0).equals("img1")) {
+ String drawableName = xpp.getAttributeValue(0);
+ mFrontImage = loadBitmap(drawableName);
+ }
+ } else if (!fallback && xpp.getName().equals("scale")) {
+ if (xpp.getAttributeCount() > 0 &&
+ xpp.getAttributeName(0).equals("factor")) {
+ mFactor = Float.valueOf(xpp.getAttributeValue(0));
+ }
+ }
+ if (xpp.getName().equals("item")) {
+ String componentName = null;
+ String drawableName = null;
+
+ for (int i = 0; i < xpp.getAttributeCount(); i++) {
+ if (xpp.getAttributeName(i).equals("component")) {
+ componentName = xpp.getAttributeValue(i);
+ } else if (xpp.getAttributeName(i).equals("drawable")) {
+ drawableName = xpp.getAttributeValue(i);
+ }
+ }
+ if (fallback && getIdentifier(packageName, drawableName,
+ true) > 0 && !mDrawables.contains(drawableName)) {
+ mDrawables.add(drawableName);
+ }
+ if (!fallback && componentName != null && drawableName != null &&
+ !mAppFilterDrawables.containsKey(componentName)) {
+ mAppFilterDrawables.put(componentName, drawableName);
+ }
+ }
+ }
+ eventType = xpp.next();
+ }
+ }
+ } catch (NameNotFoundException | XmlPullParserException | IOException e) {
+ Log.e(TAG, "Error parsing appfilter.xml " + e);
+ }
+ }
+
+ public List<String> getAllDrawables(final String packageName) {
+ loadAllDrawables(packageName);
+ Collections.sort(mDrawables, String::compareToIgnoreCase);
+ return mDrawables;
+ }
+
+ private void loadAllDrawables(String packageName) {
+ mDrawables.clear();
+ XmlPullParser xpp;
+ try {
+ Resources res = mPackageManager.getResourcesForApplication(packageName);
+ mCurrentIconPackRes = res;
+ int resource = res.getIdentifier("drawable", "xml", packageName);
+ if (resource < 0) {
+ return;
+ }
+ xpp = res.getXml(resource);
+ int eventType = xpp.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (xpp.getName().equals("item")) {
+ String drawableName = xpp.getAttributeValue(null, "drawable");
+ if (!mDrawables.contains(drawableName) &&
+ getIdentifier(packageName, drawableName, true) > 0) {
+ mDrawables.add(drawableName);
+ }
+ }
+ }
+ eventType = xpp.next();
+ }
+ } catch (NameNotFoundException | XmlPullParserException | IOException e) {
+ Log.i(TAG, "Error parsing drawable.xml for package " + packageName +
+ " trying appfilter now");
+ // fallback onto appfilter if drawable xml fails
+ loadIconPack(packageName, true);
+ }
+ }
+
+ public boolean isDefaultIconPack() {
+ return mDefaultIconPack.equalsIgnoreCase(mIconPackPackageName) ||
+ mIconPackPackageName.equals(mContext.getString(R.string.icon_pack_system));
+ }
+
+ public List<String> getMatchingDrawables(String packageName) {
+ List<String> matchingDrawables = new ArrayList<>();
+ ApplicationInfo info = null;
+ try {
+ info = mPackageManager.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException ignored) {
+
+ }
+ String packageLabel = (info != null ? mPackageManager.getApplicationLabel(info).toString()
+ : packageName).replaceAll("[^a-zA-Z]", "").toLowerCase().trim();
+ for (String drawable : mDrawables) {
+ if (drawable == null) continue;
+ String filteredDrawable = drawable.replaceAll("[^a-zA-Z]",
+ "").toLowerCase().trim();
+ if (filteredDrawable.length() > 2 && (packageLabel.contains(filteredDrawable) ||
+ filteredDrawable.contains(packageLabel))) {
+ matchingDrawables.add(drawable);
+ }
+ }
+ return matchingDrawables;
+ }
+
+ public int getIdentifier(String packageName, String drawableName, boolean currentIconPack) {
+ if (drawableName == null) {
+ return 0;
+ }
+ if (packageName == null) {
+ packageName = mIconPackPackageName;
+ }
+ return (!currentIconPack ? mOriginalIconPackRes : mCurrentIconPackRes).getIdentifier(
+ drawableName, "drawable", packageName);
+ }
+
+ public Drawable loadDrawable(String packageName, String drawableName, boolean currentIconPack) {
+ if (packageName == null) {
+ packageName = mIconPackPackageName;
+ }
+ int id = getIdentifier(packageName, drawableName, currentIconPack);
+
+ return id > 0 ?
+ (!currentIconPack ? mOriginalIconPackRes : mCurrentIconPackRes).getDrawable(id) :
+ null;
+ }
+
+ private Bitmap loadBitmap(String drawableName) {
+ Drawable bitmap = loadDrawable(null, drawableName, true);
+ if (bitmap != null && bitmap instanceof BitmapDrawable) {
+ return ((BitmapDrawable) bitmap).getBitmap();
+ }
+ return null;
+ }
+
+ private Bitmap getDefaultAppDrawable(ComponentName componentName) {
+ Drawable drawable = null;
+ try {
+ drawable = mPackageManager.getActivityIcon(componentName);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Unable to find component " + componentName.toString() + e);
+ }
+ if (drawable == null) {
+ return null;
+ }
+ if (drawable instanceof BitmapDrawable) {
+ return generateBitmap(((BitmapDrawable) drawable).getBitmap());
+ }
+
+ /*
+ * Welcome to HaxxWorld
+ * The app is using adaptive icons, but the current icon pack doesn't have a valid
+ * replacement. The original adaptive icon is loaded instead (in a non-ui thread
+ * because the called method doesn't allow doing so)
+ */
+ if (Utilities.ATLEAST_OREO) {
+ final Drawable icon = drawable;
+
+ Thread iconLoader = new Thread(() -> {
+ UserHandle userHandle = UserHandle.getUserHandleForUid(Process.myUid());
+ mTmpBitmap = LauncherIcons.createBadgedIconBitmap(icon, userHandle, mContext,
+ Build.VERSION_CODES.O);
+ });
+ iconLoader.start();
+ try {
+ iconLoader.join();
+ return mTmpBitmap;
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ return generateBitmap(Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888));
+ }
+
+ public void switchIconPacks(String packageName) {
+ if (packageName.equals(mIconPackPackageName)) {
+ packageName = mDefaultIconPack;
+ }
+
+ String localizedDefault = mContext.getString(R.string.icon_pack_system);
+ if (packageName.equals(mDefaultIconPack) || packageName.equals(localizedDefault) ||
+ mIconPacks.containsKey(packageName)) {
+ new IconPackLoader(packageName).execute();
+ }
+ }
+
+ public Bitmap getDrawableIconForPackage(ComponentName componentName) {
+ if (isDefaultIconPack()) {
+ return getDefaultAppDrawable(componentName);
+ }
+
+ // sth FUKY here
+
+ String drawableName = mAppFilterDrawables.get(componentName.toString());
+ Drawable drawable = loadDrawable(null, drawableName, false);
+ if (drawable != null && drawable instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+ cacheStoreDrawable(componentName.toString(), bitmap);
+ if (bitmap == null) {
+ }
+ return bitmap;
+ }
+
+ Bitmap cachedIcon = cacheGetDrawable(componentName.toString());
+ if (cachedIcon != null) {
+ return cachedIcon;
+ }
+
+ return getDefaultAppDrawable(componentName);
+ }
+
+ private Bitmap generateBitmap(Bitmap defaultBitmap) {
+ if (mBackImages.isEmpty()) {
+ return defaultBitmap;
+ }
+ Random random = new Random();
+ int id = random.nextInt(mBackImages.size());
+ Bitmap backImage = mBackImages.get(id);
+ int w = backImage.getWidth();
+ int h = backImage.getHeight();
+
+ Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ canvas.drawBitmap(backImage, 0, 0, null);
+
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(defaultBitmap,
+ (int) (w * mFactor), (int) (h * mFactor), false);
+
+ Bitmap mutableMask = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ Canvas maskCanvas = new Canvas(mutableMask);
+ Bitmap targetBitmap = mMaskImage == null ? mutableMask : mMaskImage;
+ maskCanvas.drawBitmap(targetBitmap, 0, 0, new Paint());
+
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ canvas.drawBitmap(scaledBitmap, (w - scaledBitmap.getWidth()) / 2,
+ (h - scaledBitmap.getHeight()) / 2, null);
+ canvas.drawBitmap(mutableMask, 0, 0, paint);
+
+ if (mFrontImage != null) {
+ canvas.drawBitmap(mFrontImage, 0, 0, null);
+ }
+ return result;
+ }
+
+ public Pair<List<String>, List<String>> getAllIconPacks() {
+ // be sure to update the icon packs list
+ loadAvailableIconPacks();
+
+ List<String> iconPackNames = new ArrayList<>();
+ List<String> iconPackLabels = new ArrayList<>();
+ List<IconPackInfo> iconPacks = new ArrayList<>(mIconPacks.values());
+ Collections.sort(iconPacks, (info, info2) ->
+ info.label.toString().compareToIgnoreCase(info2.label.toString()));
+ for (IconPackInfo info : iconPacks) {
+ iconPackNames.add(info.packageName);
+ iconPackLabels.add(info.label.toString());
+ }
+ return Pair.create(iconPackNames, iconPackLabels);
+ }
+
+ private void loadAvailableIconPacks() {
+ List<ResolveInfo> launcherActivities = new ArrayList<>();
+ mIconPacks.clear();
+
+ for (String i : LAUNCHER_INTENTS) {
+ launcherActivities.addAll(mPackageManager.queryIntentActivities(
+ new Intent(i), PackageManager.GET_META_DATA));
+ }
+ for (ResolveInfo ri : launcherActivities) {
+ String packageName = ri.activityInfo.packageName;
+ IconPackInfo info = new IconPackInfo(ri, mPackageManager);
+ mIconPacks.put(packageName, info);
+ }
+ }
+
+ private boolean isDrawableInCache(String key) {
+ File drawableFile = cacheGetFileName(key);
+ return drawableFile.isFile();
+ }
+
+ private void cacheStoreDrawable(String key, Bitmap bitmap) {
+ if (isDrawableInCache(key)) {
+ return;
+ }
+
+ File drawableFile = cacheGetFileName(key);
+
+ try (FileOutputStream fos = new FileOutputStream(drawableFile)) {
+ bitmap.compress(CompressFormat.PNG, 100, fos);
+ fos.flush();
+ fos.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to store drawable in cache " + e);
+ }
+ }
+
+ private Bitmap cacheGetDrawable(String key) {
+ if (!isDrawableInCache(key)) {
+ return null;
+ }
+
+ try (FileInputStream fis = new FileInputStream(cacheGetFileName(key))) {
+ BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(),
+ BitmapFactory.decodeStream(fis));
+ fis.close();
+ return drawable.getBitmap();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to get drawable from cache " + e);
+ }
+
+ return null;
+ }
+
+ private File cacheGetFileName(String key) {
+ return new File(getIconsCacheDir() + mIconPackPackageName
+ + "_" + key.hashCode() + ".png");
+ }
+
+ private File getIconsCacheDir() {
+ return new File(mContext.getCacheDir().getPath() + "/icons/");
+ }
+
+ private void clearCache() {
+ File cacheDir = getIconsCacheDir();
+ if (!cacheDir.isDirectory()) {
+ return;
+ }
+
+ for (File item : cacheDir.listFiles()) {
+ if (!item.delete()) {
+ Log.w(TAG, "Failed to delete file: " + item.getAbsolutePath());
+ }
+ }
+ }
+
+ public void showDialog(Activity activity) {
+ loadAvailableIconPacks();
+ final IconAdapter adapter = new IconAdapter(mContext, mIconPacks);
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity)
+ .setTitle(R.string.icon_pack_title)
+ .setAdapter(adapter, (dialog, position) -> {
+ String selected = adapter.getItem(position);
+ String current = Utilities.getPrefs(mContext.getApplicationContext())
+ .getString(SettingsActivity.KEY_ICON_PACK, mDefaultIconPack);
+ if (!selected.equals(current)) {
+ switchIconPacks(selected);
+ }
+ });
+ mAlertDialog = builder.create();
+ mAlertDialog.show();
+ mDialogShowing = true;
+ }
+
+ public void hideDialog() {
+ if (mDialogShowing && mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ mDialogShowing = false;
+ }
+ }
+
+ private 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);
+ }
+
+ private IconPackInfo(String label, Drawable icon, String packageName) {
+ this.label = label;
+ this.icon = icon;
+ this.packageName = packageName;
+ }
+ }
+
+ private static class IconAdapter extends BaseAdapter {
+ ArrayList<IconPackInfo> mSupportedPackages;
+ LayoutInflater mLayoutInflater;
+ String mCurrentIconPack;
+
+ IconAdapter(Context context, Map<String, IconPackInfo> supportedPackages) {
+ mLayoutInflater = LayoutInflater.from(context);
+ mSupportedPackages = new ArrayList<>(supportedPackages.values());
+ Collections.sort(mSupportedPackages, (lhs, rhs) ->
+ lhs.label.toString().compareToIgnoreCase(rhs.label.toString()));
+
+ Resources res = context.getResources();
+
+ Drawable icon = res.getDrawable(android.R.mipmap.sym_def_app_icon);
+ String defaultLabel = res.getString(R.string.icon_pack_system);
+
+ mSupportedPackages.add(0, new IconPackInfo(defaultLabel, icon, defaultLabel));
+ mCurrentIconPack = Utilities.getPrefs(context.getApplicationContext())
+ .getString(SettingsActivity.KEY_ICON_PACK,
+ res.getString(R.string.icon_pack_default));
+ }
+
+ @Override
+ public int getCount() {
+ return mSupportedPackages.size();
+ }
+
+ @Override
+ public String getItem(int position) {
+ return mSupportedPackages.get(position).packageName;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.target_edit_iconpack_chooser, null);
+ }
+ IconPackInfo info = mSupportedPackages.get(position);
+ TextView txtView = (TextView) convertView.findViewById(R.id.title);
+ txtView.setText(info.label);
+ ImageView imgView = (ImageView) convertView.findViewById(R.id.icon);
+ imgView.setImageDrawable(info.icon);
+ RadioButton radioButton = (RadioButton) convertView.findViewById(R.id.radio);
+ radioButton.setChecked(info.packageName.equals(mCurrentIconPack));
+ return convertView;
+ }
+ }
+
+ private class IconPackLoader extends AsyncTask<Void, Void, Void> {
+ private String mIconPackPackageName;
+
+ private IconPackLoader(String packageName) {
+ mIconPackPackageName = packageName;
+ mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ loadIconPack(mIconPackPackageName, false);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ Utilities.getPrefs(mContext.getApplicationContext()).edit()
+ .putString(SettingsActivity.KEY_ICON_PACK, mIconPackPackageName).apply();
+ mIconCache.clearIconDataBase();
+ mIconCache.flush();
+ LauncherAppState.getInstance(mContext).getModel().forceReload();
+ }
+ }
+} \ No newline at end of file