summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey <joey@lineageos.org>2020-08-05 15:52:24 +0200
committerJoey <joey@lineageos.org>2021-02-17 10:40:44 +0100
commit7f328bb343c6bdc68d6036f91a2f56a69098162b (patch)
treed9e8f9188e7636fdbd43ef3af3e590417e0b544a
parent94afb12f286e5a92ebb4c653dd5cb4a54d7b31aa (diff)
Launcher3: Add support for icon packs
- Supports icon packs from nova and adw - Live preview in settings - Default applied icon pack via overlay Change-Id: Ie9879fa30f4cd9d52356acf78a423b415657510c Signed-off-by: Joey <joey@lineageos.org>
-rw-r--r--AndroidManifest-common.xml6
-rw-r--r--res/drawable/ic_icon_pack_plus.xml26
-rw-r--r--res/layout/preference_radio.xml77
-rw-r--r--res/layout/preference_widget_icons_preview.xml49
-rw-r--r--res/layout/preference_widget_radiobutton.xml25
-rw-r--r--res/menu/menu_icon_pack.xml23
-rw-r--r--res/values/lineage_config.xml5
-rw-r--r--res/values/lineage_strings.xml6
-rw-r--r--res/xml/launcher_preferences.xml4
-rw-r--r--src/com/android/launcher3/InvariantDeviceProfile.java7
-rw-r--r--src/com/android/launcher3/icons/IconProvider.java18
-rw-r--r--src/com/android/launcher3/lineage/icon/GetLaunchableInfoTask.java83
-rw-r--r--src/com/android/launcher3/lineage/icon/IconPack.java220
-rw-r--r--src/com/android/launcher3/lineage/icon/IconPackHeaderPreference.java98
-rw-r--r--src/com/android/launcher3/lineage/icon/IconPackSettingsActivity.java133
-rw-r--r--src/com/android/launcher3/lineage/icon/IconPackSettingsFragment.java156
-rw-r--r--src/com/android/launcher3/lineage/icon/IconPackStore.java70
-rw-r--r--src/com/android/launcher3/lineage/icon/LineageIconFactory.java110
-rw-r--r--src/com/android/launcher3/lineage/icon/providers/IconPackProvider.java172
-rw-r--r--src/com/android/launcher3/lineage/settings/RadioHeaderPreference.java45
-rw-r--r--src/com/android/launcher3/lineage/settings/RadioPreference.java45
-rw-r--r--src/com/android/launcher3/lineage/settings/RadioSettingsFragment.java142
-rw-r--r--src/com/android/launcher3/settings/SettingsActivity.java46
-rw-r--r--src/com/android/launcher3/util/ConfigMonitor.java16
24 files changed, 1578 insertions, 4 deletions
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 4e4ebf993..2d4e1cfff 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -174,6 +174,12 @@
</intent-filter>
</activity>
+ <activity
+ android:name="com.android.launcher3.lineage.icon.IconPackSettingsActivity"
+ android:label="@string/icon_pack_title"
+ android:theme="@android:style/Theme.DeviceDefault.Settings"
+ android:autoRemoveFromRecents="true" />
+
<provider
android:name="com.android.launcher3.testing.TestInformationProvider"
android:authorities="${packageName}.TestInfo"
diff --git a/res/drawable/ic_icon_pack_plus.xml b/res/drawable/ic_icon_pack_plus.xml
new file mode 100644
index 000000000..2686fe476
--- /dev/null
+++ b/res/drawable/ic_icon_pack_plus.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2019 The LineageOS 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.
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M 19 3 L 5 3 C 3.89 3 3 3.9 3 5 L 3 19 C 3 20.1 3.89 21 5 21 L 19 21 C 20.1 21 21 20.1 21 19 L 21 5 C 21 3.9 20.1 3 19 3 Z M 19 19 L 5 19 L 5 5 L 19 5 L 19 19 Z M 11 17 L 13 17 L 13 13 L 17 13 L 17 11 L 13 11 L 13 7 L 11 7 L 11 11 L 7 11 L 7 13 L 11 13 Z"
+ android:fillColor="?android:attr/textColorPrimary"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/res/layout/preference_radio.xml b/res/layout/preference_radio.xml
new file mode 100644
index 000000000..9cdacad25
--- /dev/null
+++ b/res/layout/preference_radio.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source 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.
+-->
+<!-- This file is copied from preference_app.xml with modification to
+ support widget on the opposite side horizontally -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:minWidth="56dp"
+ android:layout_marginEnd="16dp"
+ android:orientation="vertical" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/summary_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ <TextView android:id="@android:id/summary"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <TextView android:id="@+id/appendix"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
+ android:textAlignment="viewEnd"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="1"
+ android:ellipsize="end" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/preference_widget_icons_preview.xml b/res/layout/preference_widget_icons_preview.xml
new file mode 100644
index 000000000..c87819b44
--- /dev/null
+++ b/res/layout/preference_widget_icons_preview.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 Shift GmbH
+
+ 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingBottom="24dp"
+ android:paddingTop="24dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <ImageView
+ android:id="@+id/pref_icon_a"
+ android:layout_height="56dp"
+ android:layout_width="56dp"
+ android:layout_marginHorizontal="16dp" />
+ <ImageView
+ android:id="@+id/pref_icon_b"
+ android:layout_height="56dp"
+ android:layout_width="56dp"
+ android:layout_marginHorizontal="16dp" />
+ <ImageView
+ android:id="@+id/pref_icon_c"
+ android:layout_height="56dp"
+ android:layout_width="56dp"
+ android:layout_marginHorizontal="16dp" />
+ <ImageView
+ android:id="@+id/pref_icon_d"
+ android:layout_height="56dp"
+ android:layout_width="56dp"
+ android:layout_marginHorizontal="16dp" />
+</LinearLayout>
diff --git a/res/layout/preference_widget_radiobutton.xml b/res/layout/preference_widget_radiobutton.xml
new file mode 100644
index 000000000..c25ed17b6
--- /dev/null
+++ b/res/layout/preference_widget_radiobutton.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source 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.
+-->
+<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
+ inside android.R.layout.preference. -->
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@null"
+ android:focusable="false"
+ android:clickable="false" />
diff --git a/res/menu/menu_icon_pack.xml b/res/menu/menu_icon_pack.xml
new file mode 100644
index 000000000..30e04fae2
--- /dev/null
+++ b/res/menu/menu_icon_pack.xml
@@ -0,0 +1,23 @@
+<!--
+ Copyright (C) 2019 The LineageOS 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/menu_icon_pack"
+ android:icon="@drawable/ic_icon_pack_plus"
+ android:showAsAction="always"
+ android:title="@string/icon_pack_add" />
+</menu>
diff --git a/res/values/lineage_config.xml b/res/values/lineage_config.xml
index 7067ca880..db8620f6c 100644
--- a/res/values/lineage_config.xml
+++ b/res/values/lineage_config.xml
@@ -16,4 +16,9 @@
<resources>
<string name="pref_show_google_now_summary" translatable="false">@string/msg_minus_one_on_left</string>
+
+ <!-- Icon pack -->
+ <string name="icon_pack_settings_class" translatable="false">com.android.launcher3.lineage.icon.IconPackSettingsFragment</string>
+ <!-- Default icon pack package. Set to "android" to use default / original icons -->
+ <string name="icon_pack_default_pkg" translatable="false">android</string>
</resources>
diff --git a/res/values/lineage_strings.xml b/res/values/lineage_strings.xml
index 2e602ffb4..506d9bf30 100644
--- a/res/values/lineage_strings.xml
+++ b/res/values/lineage_strings.xml
@@ -49,4 +49,10 @@
<string name="trust_apps_help">Help</string>
<string name="trust_apps_info_hidden">Hidden apps and their widgets are hidden from the drawer</string>
<string name="trust_apps_info_protected">Protected apps require authentication to be opened from the launcher</string>
+
+ <!-- Icon pack -->
+ <string name="icon_pack_title">Icon pack</string>
+ <string name="icon_pack_default_label">Default</string>
+ <string name="icon_pack_add">Install more</string>
+ <string name="icon_pack_no_market">There\'s no available app store</string>
</resources>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index b9801fe4d..36eb21363 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -62,6 +62,10 @@
android:key="pref_trust_apps"
android:title="@string/trust_apps_manager_name" />
+ <Preference
+ android:key="pref_icon_pack"
+ android:title="@string/icon_pack_title" />
+
<androidx.preference.PreferenceScreen
android:key="pref_developer_options"
android:persistent="false"
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 02f9b8ff3..16e998390 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -49,6 +49,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.lineage.icon.IconPackStore;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.DefaultDisplay.Info;
@@ -109,6 +110,7 @@ public class InvariantDeviceProfile implements OnSharedPreferenceChangeListener
public int numFolderRows;
public int numFolderColumns;
public float iconSize;
+ public String iconPack;
public String iconShapePath;
public float landscapeIconSize;
public int iconBitmapSize;
@@ -154,6 +156,7 @@ public class InvariantDeviceProfile implements OnSharedPreferenceChangeListener
numFolderRows = p.numFolderRows;
numFolderColumns = p.numFolderColumns;
iconSize = p.iconSize;
+ iconPack = p.iconPack;
iconShapePath = p.iconShapePath;
landscapeIconSize = p.landscapeIconSize;
iconBitmapSize = p.iconBitmapSize;
@@ -277,6 +280,7 @@ public class InvariantDeviceProfile implements OnSharedPreferenceChangeListener
iconSize = displayOption.iconSize;
iconShapePath = getIconShapePath(context);
+ iconPack = new IconPackStore(context).getCurrent();
landscapeIconSize = displayOption.landscapeIconSize;
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
iconTextSize = displayOption.iconTextSize;
@@ -376,7 +380,8 @@ public class InvariantDeviceProfile implements OnSharedPreferenceChangeListener
}
if (iconSize != oldProfile.iconSize || iconBitmapSize != oldProfile.iconBitmapSize ||
- !iconShapePath.equals(oldProfile.iconShapePath)) {
+ !iconShapePath.equals(oldProfile.iconShapePath) ||
+ !iconPack.equals(oldProfile.iconPack)) {
changeFlags |= CHANGE_FLAG_ICON_PARAMS;
}
if (!iconShapePath.equals(oldProfile.iconShapePath)) {
diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java
index 1468b2782..ab4edb9e1 100644
--- a/src/com/android/launcher3/icons/IconProvider.java
+++ b/src/com/android/launcher3/icons/IconProvider.java
@@ -38,6 +38,8 @@ import com.android.launcher3.icons.BitmapInfo.Extender;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.lineage.icon.IconPack;
+import com.android.launcher3.lineage.icon.providers.IconPackProvider;
import java.util.Calendar;
import java.util.function.BiConsumer;
@@ -125,7 +127,11 @@ public class IconProvider {
&& Process.myUserHandle().equals(user)) {
icon = loadClockDrawable(0);
}
- return icon == null ? loader.apply(obj, param) : icon;
+ icon = getFromIconPack(icon, packageName);
+ if (icon == null) {
+ icon = loader.apply(obj, param);
+ }
+ return icon;
}
private Drawable loadCalendarDrawable(int iconDpi) {
@@ -248,4 +254,14 @@ public class IconProvider {
return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
}
+
+ private Drawable getFromIconPack(Drawable icon, String packageName) {
+ final IconPack iconPack = IconPackProvider.loadAndGetIconPack(mContext);
+ if (iconPack == null) {
+ return null;
+ }
+
+ final Drawable iconMask = iconPack.getIcon(packageName, null, "");
+ return iconMask == null ? icon : iconMask;
+ }
}
diff --git a/src/com/android/launcher3/lineage/icon/GetLaunchableInfoTask.java b/src/com/android/launcher3/lineage/icon/GetLaunchableInfoTask.java
new file mode 100644
index 000000000..824339baa
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/GetLaunchableInfoTask.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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 com.android.launcher3.lineage.icon;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+final class GetLaunchableInfoTask extends AsyncTask<Void, Void, List<LauncherActivityInfo>> {
+
+ private final PackageManager pm;
+ private final LauncherApps launcherApps;
+ private final int limit;
+ private final Callback callback;
+
+ GetLaunchableInfoTask(PackageManager pm,
+ LauncherApps launcherApps,
+ int limit,
+ Callback callback) {
+ this.pm = pm;
+ this.launcherApps = launcherApps;
+ this.limit = limit;
+ this.callback = callback;
+ }
+
+ @Override
+ protected List<LauncherActivityInfo> doInBackground(Void... voids) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ // This should never happen
+ return new ArrayList<>();
+ }
+
+ final UserHandle ua = Process.myUserHandle();
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null)
+ .addCategory(Intent.CATEGORY_LAUNCHER);
+ return pm.queryIntentActivities(mainIntent, 0)
+ .parallelStream()
+ .sorted(new ResolveInfo.DisplayNameComparator(pm))
+ .limit(limit)
+ .map((ri) -> {
+ final ActivityInfo ai = ri.activityInfo;
+ final Intent i = new Intent();
+ i.setClassName(ai.applicationInfo.packageName, ai.name);
+ return launcherApps.resolveActivity(i, ua);
+ })
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ protected void onPostExecute(List<LauncherActivityInfo> list) {
+ callback.onLoadCompleted(list);
+ }
+
+ interface Callback {
+ void onLoadCompleted(List<LauncherActivityInfo> result);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/icon/IconPack.java b/src/com/android/launcher3/lineage/icon/IconPack.java
new file mode 100644
index 000000000..04b8cd891
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/IconPack.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2019 Paranoid Android
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.icon;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+import com.android.launcher3.lineage.icon.providers.IconPackProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public final class IconPack {
+ /*
+ * Useful Links:
+ * https://github.com/teslacoil/Example_NovaTheme
+ * http://stackoverflow.com/questions/7205415/getting-resources-of-another-application
+ * http://stackoverflow.com/questions/3890012/how-to-access-string-resource-from-another-application
+ */
+
+ private final Context context;
+ private final String packageName;
+
+ private Map<String, String> iconPackResources;
+ private List<String> iconBackStrings;
+ private List<Drawable> iconBackList;
+ private Drawable iconUpon;
+ private Drawable iconMask;
+ private Resources loadedIconPackResource;
+ private float iconScale;
+
+ public IconPack(Context context, String packageName){
+ this.context = context;
+ this.packageName = packageName;
+ }
+
+ public void setIcons(Map<String, String> iconPackResources, List<String> iconBackStrings) {
+ this.iconPackResources = iconPackResources;
+ this.iconBackStrings = iconBackStrings;
+ iconBackList = new ArrayList<Drawable>();
+ try {
+ loadedIconPackResource = context.getPackageManager()
+ .getResourcesForApplication(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ // must never happen cause it's checked already in the provider
+ return;
+ }
+
+ iconMask = getDrawableForName(IconPackProvider.ICON_MASK_TAG);
+ iconUpon = getDrawableForName(IconPackProvider.ICON_UPON_TAG);
+ for (int i = 0; i < iconBackStrings.size(); i++) {
+ final String backIconString = iconBackStrings.get(i);
+ final Drawable backIcon = getDrawableWithName(backIconString);
+ if (backIcon != null) {
+ iconBackList.add(backIcon);
+ }
+ }
+
+ final String scale = iconPackResources.get(IconPackProvider.ICON_SCALE_TAG);
+ if (scale != null) {
+ try {
+ iconScale = Float.valueOf(scale);
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+
+ public Drawable getIcon(LauncherActivityInfo info, Drawable appIcon, CharSequence appLabel) {
+ return getIcon(info.getComponentName(), appIcon, appLabel);
+ }
+
+ public Drawable getIcon(ActivityInfo info, Drawable appIcon, CharSequence appLabel) {
+ return getIcon(new ComponentName(info.packageName, info.name), appIcon, appLabel);
+ }
+
+ public Drawable getIcon(ComponentName name, Drawable appIcon, CharSequence appLabel) {
+ return getDrawable(name.flattenToString(), appIcon, appLabel);
+ }
+
+ public Drawable getIcon(String packageName, Drawable appIcon, CharSequence appLabel) {
+ return getDrawable(packageName, appIcon, appLabel);
+ }
+
+ private Drawable getDrawable(String name, Drawable appIcon, CharSequence appLabel) {
+ Drawable d = getDrawableForName(name);
+ if (d == null && appIcon != null) {
+ d = compose(name, appIcon, appLabel);
+ }
+ return d;
+ }
+
+ private Drawable getIconBackFor(CharSequence tag) {
+ if (iconBackList == null || iconBackList.size() == 0) {
+ return null;
+ }
+
+ if (iconBackList.size() == 1) {
+ return iconBackList.get(0);
+ }
+
+ try {
+ final Drawable back = iconBackList.get(
+ (tag.hashCode() & 0x7fffffff) % iconBackList.size());
+ return back;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return iconBackList.get(0);
+ }
+ }
+
+ private int getResourceIdForDrawable(String resource) {
+ return loadedIconPackResource.getIdentifier(resource, "drawable", packageName);
+ }
+
+ private Drawable getDrawableForName(String name) {
+ final String item = iconPackResources.get(name);
+ if (TextUtils.isEmpty(item)) {
+ return null;
+ }
+
+ final int id = getResourceIdForDrawable(item);
+ return id == 0 ? null : loadedIconPackResource.getDrawable(id);
+ }
+
+ private Drawable getDrawableWithName(String name) {
+ final int id = getResourceIdForDrawable(name);
+ return id == 0 ? null : loadedIconPackResource.getDrawable(id);
+ }
+
+ private BitmapDrawable getBitmapDrawable(Drawable image) {
+ if (image instanceof BitmapDrawable) {
+ return (BitmapDrawable) image;
+ }
+
+ final Canvas canvas = new Canvas();
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ final Bitmap bmResult = Bitmap.createBitmap(image.getIntrinsicWidth(),
+ image.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(bmResult);
+ image.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ image.draw(canvas);
+ return new BitmapDrawable(loadedIconPackResource, bmResult);
+ }
+
+ private Drawable compose(String name, Drawable appIcon, CharSequence appLabel) {
+ final Canvas canvas = new Canvas();
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ final BitmapDrawable appIconBitmap = getBitmapDrawable(appIcon);
+ final int width = appIconBitmap.getBitmap().getWidth();
+ final int height = appIconBitmap.getBitmap().getHeight();
+ float scale = iconScale;
+ final Drawable iconBack = getIconBackFor(appLabel);
+ if (iconBack == null && iconMask == null && iconUpon == null){
+ scale = 1.0f;
+ }
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(bitmap);
+ final int scaledWidth = (int) (width * scale);
+ final int scaledHeight = (int) (height * scale);
+ if (scaledWidth != width || scaledHeight != height) {
+ final Bitmap scaledBitmap = Bitmap.createScaledBitmap(
+ appIconBitmap.getBitmap(), scaledWidth, scaledHeight, true);
+ canvas.drawBitmap(scaledBitmap, (width - scaledWidth) / 2,
+ (height - scaledHeight) / 2, null);
+ } else {
+ canvas.drawBitmap(appIconBitmap.getBitmap(), 0, 0, null);
+ }
+
+ if (iconMask != null) {
+ iconMask.setBounds(0, 0, width, height);
+ BitmapDrawable b = getBitmapDrawable(iconMask);
+ b.getPaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ b.draw(canvas);
+ }
+ if (iconBack != null) {
+ iconBack.setBounds(0, 0, width, height);
+ BitmapDrawable b = getBitmapDrawable(iconBack);
+ b.getPaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
+ b.draw(canvas);
+ }
+ if (iconUpon != null) {
+ iconUpon.setBounds(0, 0, width, height);
+ iconUpon.draw(canvas);
+ }
+
+ return new BitmapDrawable(loadedIconPackResource, bitmap);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/icon/IconPackHeaderPreference.java b/src/com/android/launcher3/lineage/icon/IconPackHeaderPreference.java
new file mode 100644
index 000000000..fdb8dc155
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/IconPackHeaderPreference.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.icon;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.view.View;
+import android.widget.ImageView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.lineage.settings.RadioHeaderPreference;
+
+
+public class IconPackHeaderPreference extends RadioHeaderPreference {
+ private static final String TAG = "IconPackHeaderPreference";
+ private static final int PREVIEW_ICON_NUM = 4;
+ // This value has been selected as an average of usual "device profile-computed" values
+ private static final int PREVIEW_ICON_DPI = 500;
+
+ private final Context context;
+ private ImageView[] icons = null;
+
+ public IconPackHeaderPreference(Context context) {
+ this(context, null);
+ }
+
+ public IconPackHeaderPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public IconPackHeaderPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.context = context;
+
+ setLayoutResource(R.layout.preference_widget_icons_preview);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ final ImageView[] imageViews = {
+ (ImageView) holder.findViewById(R.id.pref_icon_a),
+ (ImageView) holder.findViewById(R.id.pref_icon_b),
+ (ImageView) holder.findViewById(R.id.pref_icon_c),
+ (ImageView) holder.findViewById(R.id.pref_icon_d)
+ };
+ this.icons = imageViews;
+ onRadioElementSelected(null);
+ }
+
+ @Override
+ public void onDetached() {
+ this.icons = null;
+ super.onDetached();
+ }
+
+ @Override
+ public void onRadioElementSelected(String key) {
+ if (icons == null) {
+ return;
+ }
+
+ final IconProvider iconProvider = new IconProvider(context);
+ final PackageManager pm = context.getPackageManager();
+ final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ new GetLaunchableInfoTask(pm, launcherApps, PREVIEW_ICON_NUM, (aiList) -> {
+ for (int i = 0; i < icons.length; i++) {
+ icons[i].setImageDrawable(iconProvider.getIcon(
+ aiList.get(i), PREVIEW_ICON_DPI));
+ }
+ }).execute();
+ }
+}
diff --git a/src/com/android/launcher3/lineage/icon/IconPackSettingsActivity.java b/src/com/android/launcher3/lineage/icon/IconPackSettingsActivity.java
new file mode 100644
index 000000000..b7bd04afd
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/IconPackSettingsActivity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.icon;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceScreen;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class IconPackSettingsActivity extends Activity implements
+ OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ if (savedInstanceState == null) {
+ final Fragment f = Fragment.instantiate(this,
+ getString(R.string.icon_pack_settings_class), null);
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, f)
+ .commit();
+ }
+ }
+
+ private boolean startFragment(String fragment, Bundle args, String key) {
+ if (getFragmentManager().isStateSaved()) {
+ // Sometimes onClick can come after onPause because of being posted on the handler.
+ // Skip starting new fragments in that case.
+ return false;
+ }
+
+ final Fragment f = Fragment.instantiate(this, fragment, args);
+ getFragmentManager()
+ .beginTransaction()
+ .replace(android.R.id.content, f)
+ .addToBackStack(key)
+ .commit();
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragment preferenceFragment,
+ Preference pref) {
+ return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+ Bundle args = new Bundle();
+ args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ return startFragment(getString(R.string.icon_pack_settings_class),
+ args, pref.getKey());
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ final MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.menu_icon_pack, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ finish();
+ return true;
+ } else if (id == R.id.menu_icon_pack) {
+ return openMarket();
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private boolean openMarket() {
+ final String query = getString(R.string.icon_pack_title);
+ final Intent intent = PackageManagerHelper.getMarketSearchIntent(this, query);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (intent.resolveActivity(getPackageManager()) == null) {
+ Toast.makeText(this, R.string.icon_pack_no_market, Toast.LENGTH_LONG)
+ .show();
+ return false;
+ }
+
+ startActivity(intent);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/lineage/icon/IconPackSettingsFragment.java b/src/com/android/launcher3/lineage/icon/IconPackSettingsFragment.java
new file mode 100644
index 000000000..f291504a4
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/IconPackSettingsFragment.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.icon;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.preference.Preference;
+
+import com.android.launcher3.R;
+import com.android.launcher3.lineage.settings.RadioPreference;
+import com.android.launcher3.lineage.settings.RadioSettingsFragment;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public final class IconPackSettingsFragment extends RadioSettingsFragment {
+ private static final IntentFilter PKG_UPDATE_INTENT = new IntentFilter();
+ static {
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_INSTALL);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_ADDED);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ PKG_UPDATE_INTENT.addDataScheme("package");
+ }
+
+ private IconPackStore iconPackStore = null;
+ private BroadcastReceiver broadCastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadPreferences();
+ }
+ };
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getActivity().registerReceiver(broadCastReceiver, PKG_UPDATE_INTENT);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(broadCastReceiver);
+ }
+
+ @Override
+ protected List<RadioPreference> getRadioPreferences(Context context) {
+ iconPackStore = new IconPackStore(context);
+ final String currentIconPack = iconPackStore.getCurrent();
+ final List<RadioPreference> prefsList = new ArrayList<>();
+ final Set<IconPackInfo> iconPacks = getAvailableIconPacks(context);
+
+ for (final IconPackInfo entry : iconPacks) {
+ final boolean isCurrent = currentIconPack.equals(entry.pkgName);
+ final RadioPreference pref = buildPreference(context,
+ entry.pkgName, entry.label, isCurrent);
+ prefsList.add(pref);
+
+ if (isCurrent) {
+ setSelectedPreference(pref);
+ }
+ }
+
+ return prefsList;
+ }
+
+ @Override
+ public void onSelected(String key) {
+ if (iconPackStore != null) {
+ iconPackStore.setCurrent(key);
+ }
+ super.onSelected(key);
+ }
+
+ @Override
+ protected IconPackHeaderPreference getHeader(Context context) {
+ return new IconPackHeaderPreference(context);
+ }
+
+ private Set<IconPackInfo> getAvailableIconPacks(Context context) {
+ final PackageManager pm = context.getPackageManager();
+ final Set<IconPackInfo> availablePacks = new LinkedHashSet<>();
+ final List<ResolveInfo> eligiblePacks = new ArrayList<>();
+ eligiblePacks.addAll(pm.queryIntentActivities(
+ new Intent("com.novalauncher.THEME"), 0));
+ eligiblePacks.addAll(pm.queryIntentActivities(
+ new Intent("org.adw.launcher.icons.ACTION_PICK_ICON"), 0));
+
+ // Add default
+ final String defaultLabel = context.getString(R.string.icon_pack_default_label);
+ availablePacks.add(new IconPackInfo(IconPackStore.SYSTEM_ICON_PACK, defaultLabel));
+ // Add user-installed packs
+ for (final ResolveInfo r : eligiblePacks) {
+ availablePacks.add(new IconPackInfo(
+ r.activityInfo.packageName, (String) r.loadLabel(pm)));
+ }
+ return availablePacks;
+ }
+
+ private RadioPreference buildPreference(Context context, String pkgName,
+ String label, boolean isChecked) {
+ final RadioPreference pref = new RadioPreference(context);
+ pref.setKey(pkgName);
+ pref.setTitle(label);
+ pref.setPersistent(false);
+ pref.setChecked(isChecked);
+ return pref;
+ }
+
+ private static class IconPackInfo {
+ final String pkgName;
+ final String label;
+
+ IconPackInfo(String pkgName, String label) {
+ this.pkgName = pkgName;
+ this.label = label;
+ }
+
+ @Override
+ public int hashCode() {
+ return pkgName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) return false;
+ if (!(other instanceof IconPackInfo)) return false;
+ return pkgName.equals(((IconPackInfo) other).pkgName);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/lineage/icon/IconPackStore.java b/src/com/android/launcher3/lineage/icon/IconPackStore.java
new file mode 100644
index 000000000..fd25fb703
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/IconPackStore.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.icon;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.android.launcher3.R;
+
+public final class IconPackStore {
+ public static final String SYSTEM_ICON_PACK = "android";
+ public static final String KEY_ICON_PACK = "pref_iconPackPackage";
+
+ private Context context;
+ private SharedPreferences prefs;
+
+ public IconPackStore(Context context) {
+ this.context = context;
+ this.prefs = context.getSharedPreferences(
+ "com.android.launcher3.prefs", Context.MODE_PRIVATE);
+ }
+
+ public String getCurrent() {
+ return prefs.getString(KEY_ICON_PACK, getDefaultIconPack());
+ }
+
+ public void setCurrent(String pkgName) {
+ prefs.edit()
+ .putString(KEY_ICON_PACK, pkgName)
+ .apply();
+ }
+
+ public boolean isUsingSystemIcons() {
+ return SYSTEM_ICON_PACK.equals(getCurrent());
+ }
+
+ public String getCurrentLabel(String defaultLabel) {
+ final String pkgName = getCurrent();
+ if (SYSTEM_ICON_PACK.equals(pkgName)) {
+ return defaultLabel;
+ }
+
+ final PackageManager pm = context.getPackageManager();
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
+ return (String) pm.getApplicationLabel(ai);
+ } catch (PackageManager.NameNotFoundException e) {
+ return defaultLabel;
+ }
+ }
+
+ private String getDefaultIconPack() {
+ return context.getString(R.string.icon_pack_default_pkg);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/icon/LineageIconFactory.java b/src/com/android/launcher3/lineage/icon/LineageIconFactory.java
new file mode 100644
index 000000000..3e30c4360
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/LineageIconFactory.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 The LineageOS Project
+ * Copyright (C) 2021 Shift GmbH
+ *
+ * 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.lineage.icon;
+
+
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.icons.BaseIconFactory;
+
+public class LineageIconFactory /* extends BaseIconFactory */{
+/*
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final IconPackStore mIconPackStore;
+
+ public LineageIconFactory(Context context) {
+ mContext = context;
+ mPm = context.getPackageManager();
+ mIconPackStore = new IconPackStore(context);
+ }
+
+ @Override
+ public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
+ boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
+ if (scale == null) {
+ scale = new float[1];
+ }
+ }
+
+ @Override
+ public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
+ RectF iconBounds = new RectF();
+ float[] scale = new float[1];
+ icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
+ return createIconBitmap(icon,
+ Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+ }
+
+ private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
+ boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
+ if (icon == null) {
+ return null;
+ }
+ float scale = 1f;
+
+ final boolean defaultIcons = mIconPackStore.isUsingSystemIcons();
+ if (shrinkNonAdaptiveIcons && ATLEAST_OREO && defaultIcons) {
+ if (mWrapperIcon == null) {
+ mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
+ .mutate();
+ }
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
+ dr.setBounds(0, 0, 1, 1);
+ boolean[] outShape = new boolean[1];
+ scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
+ if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
+ FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+ fsd.setDrawable(icon);
+ fsd.setScale(scale);
+ icon = dr;
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+
+ ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
+ }
+ } else {
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+ }
+
+ outScale[0] = scale;
+ return icon;
+ }
+*/
+}
diff --git a/src/com/android/launcher3/lineage/icon/providers/IconPackProvider.java b/src/com/android/launcher3/lineage/icon/providers/IconPackProvider.java
new file mode 100644
index 000000000..dfedf0607
--- /dev/null
+++ b/src/com/android/launcher3/lineage/icon/providers/IconPackProvider.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source 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 com.android.launcher3.lineage.icon.providers;
+
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.lineage.icon.IconPack;
+import com.android.launcher3.lineage.icon.IconPackStore;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class IconPackProvider {
+ private static final String TAG = "IconPackProvider";
+
+ private static Map<String, IconPack> iconPacks = new ArrayMap<>();
+
+ public static final String ICON_MASK_TAG = "iconmask";
+ public static final String ICON_BACK_TAG = "iconback";
+ public static final String ICON_UPON_TAG = "iconupon";
+ public static final String ICON_SCALE_TAG = "scale";
+
+ private IconPackProvider() {
+ }
+
+ public static IconPack getIconPack(String packageName){
+ return iconPacks.get(packageName);
+ }
+
+ public static IconPack loadAndGetIconPack(Context context) {
+ final String packageName = new IconPackStore(context).getCurrent();
+ if (IconPackStore.SYSTEM_ICON_PACK.equals(packageName)){
+ return null;
+ }
+
+ if (!iconPacks.containsKey(packageName)){
+ loadIconPack(context, packageName);
+ }
+ return getIconPack(packageName);
+ }
+
+ public static void loadIconPack(Context context, String packageName) {
+ if (IconPackStore.SYSTEM_ICON_PACK.equals(packageName)){
+ iconPacks.put("", null);
+ }
+
+ try {
+ final XmlPullParser appFilter = getAppFilter(context, packageName);
+ if (appFilter != null) {
+ final IconPack pack = new IconPack(context, packageName);
+ parseAppFilter(packageName, appFilter, pack);
+ iconPacks.put(packageName, pack);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Invalid IconPack", e);
+ return;
+ }
+ }
+
+ private static void parseAppFilter(String packageName, XmlPullParser parser,
+ IconPack pack) throws Exception {
+ final Map<String, String> iconPackResources = new HashMap<>();
+ final List<String> iconBackStrings = new ArrayList<>();
+
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String name = parser.getName();
+ if (name.equals("item")) {
+ String component = parser.getAttributeValue(null, "component");
+ final String drawable = parser.getAttributeValue(null, "drawable");
+ // 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);
+ if (!component.contains("/")) {
+ // Package icon reference
+ iconPackResources.put(component, drawable);
+ } else {
+ final ComponentName componentName = ComponentName.unflattenFromString(
+ component);
+ if (componentName != null) {
+ iconPackResources.put(componentName.getPackageName(), drawable);
+ iconPackResources.put(component, drawable);
+ }
+ }
+ continue;
+ }
+
+ if (name.equalsIgnoreCase(ICON_BACK_TAG)) {
+ final String icon = parser.getAttributeValue(null, "img");
+ if (icon == null) {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ iconBackStrings.add(parser.getAttributeValue(i));
+ }
+ }
+ continue;
+ }
+
+ if (name.equalsIgnoreCase(ICON_MASK_TAG) || name.equalsIgnoreCase(ICON_UPON_TAG)) {
+ String icon = parser.getAttributeValue(null, "img");
+ if (icon == null) {
+ if (parser.getAttributeCount() > 0) {
+ icon = parser.getAttributeValue(0);
+ }
+ }
+ iconPackResources.put(parser.getName().toLowerCase(), icon);
+ continue;
+ }
+
+ if (name.equalsIgnoreCase(ICON_SCALE_TAG)) {
+ String factor = parser.getAttributeValue(null, "factor");
+ if (factor == null) {
+ if (parser.getAttributeCount() > 0) {
+ factor = parser.getAttributeValue(0);
+ }
+ }
+ iconPackResources.put(parser.getName().toLowerCase(), factor);
+ continue;
+ }
+ }
+
+ pack.setIcons(iconPackResources, iconBackStrings);
+ }
+
+ private static XmlPullParser getAppFilter(Context context, String packageName) {
+ try {
+ final Resources res = context.getPackageManager()
+ .getResourcesForApplication(packageName);
+ final int resourceId = res.getIdentifier("appfilter", "xml", packageName);
+ if (0 != resourceId) {
+ return context.getPackageManager().getXml(packageName, resourceId, null);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to get AppFilter", e);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/lineage/settings/RadioHeaderPreference.java b/src/com/android/launcher3/lineage/settings/RadioHeaderPreference.java
new file mode 100644
index 000000000..d72abfcb1
--- /dev/null
+++ b/src/com/android/launcher3/lineage/settings/RadioHeaderPreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.settings;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+
+import com.android.launcher3.R;
+
+public abstract class RadioHeaderPreference extends Preference {
+
+ public RadioHeaderPreference(Context context) {
+ this(context, null);
+ }
+
+ public RadioHeaderPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public RadioHeaderPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setIconSpaceReserved(false);
+ }
+
+ public abstract void onRadioElementSelected(String key);
+}
diff --git a/src/com/android/launcher3/lineage/settings/RadioPreference.java b/src/com/android/launcher3/lineage/settings/RadioPreference.java
new file mode 100644
index 000000000..bbedbf572
--- /dev/null
+++ b/src/com/android/launcher3/lineage/settings/RadioPreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.settings;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.CheckBoxPreference;
+
+import com.android.launcher3.R;
+
+public class RadioPreference extends CheckBoxPreference {
+
+ public RadioPreference(Context context) {
+ this(context, null);
+ }
+
+ public RadioPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public RadioPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
+ setLayoutResource(R.layout.preference_radio);
+ setIconSpaceReserved(false);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/settings/RadioSettingsFragment.java b/src/com/android/launcher3/lineage/settings/RadioSettingsFragment.java
new file mode 100644
index 000000000..293558031
--- /dev/null
+++ b/src/com/android/launcher3/lineage/settings/RadioSettingsFragment.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.lineage.settings;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import java.util.List;
+
+public abstract class RadioSettingsFragment extends PreferenceFragment implements
+ Preference.OnPreferenceClickListener {
+ private RadioPreference selectedPreference = null;
+ private RadioHeaderPreference headerPref = null;
+
+ protected abstract List<RadioPreference> getRadioPreferences(Context context);
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ final PreferenceManager prefManager = getPreferenceManager();
+ final Context context = prefManager.getContext();
+ final PreferenceScreen screen = prefManager.createPreferenceScreen(context);
+
+ headerPref = getHeader(context);
+ if (headerPref != null) {
+ screen.addPreference(headerPref);
+ }
+
+ loadRadioPreferences(context, screen, null);
+ setPreferenceScreen(screen);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference instanceof RadioPreference) {
+ onSelected(preference.getKey());
+
+ if (selectedPreference != null) {
+ selectedPreference.setChecked(false);
+ }
+ selectedPreference = (RadioPreference) preference;
+ selectedPreference.setChecked(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ selectedPreference = null;
+ headerPref = null;
+ super.onDestroyView();
+ }
+
+ protected RadioHeaderPreference getHeader(Context context) {
+ return null;
+ }
+
+ protected final void setSelectedPreference(RadioPreference preference) {
+ selectedPreference = preference;
+ }
+
+ protected void onSelected(String key) {
+ if (headerPref != null) {
+ headerPref.onRadioElementSelected(key);
+ }
+ }
+
+ protected void reloadPreferences() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ return;
+ }
+
+ // Save current key for later
+ final String currentKey = selectedPreference == null ?
+ null : selectedPreference.getKey();
+ selectedPreference = null;
+
+ // Reload header contents
+ if (headerPref != null) {
+ headerPref.onRadioElementSelected(currentKey);
+ }
+
+ // Remove radio preferences (backwards so we don't mess up indices)
+ final int numPreferences = screen.getPreferenceCount();
+ for (int i = numPreferences - 1; i >= 0; i--) {
+ final Preference p = screen.getPreference(i);
+ if (p instanceof RadioPreference) {
+ screen.removePreference(p);
+ }
+ }
+
+ // Add radio preferences
+ final PreferenceManager prefManager = getPreferenceManager();
+ final Context context = prefManager.getContext();
+ loadRadioPreferences(context, screen, currentKey);
+ }
+
+ private void loadRadioPreferences(Context context, PreferenceScreen screen,
+ String currentKey) {
+ boolean hasSetNewCurrent = false;
+
+ final List<RadioPreference> prefs = getRadioPreferences(context);
+ for (final RadioPreference p : prefs) {
+ if (currentKey != null && currentKey.equals(p.getKey())) {
+ p.setChecked(true);
+ selectedPreference = p;
+ hasSetNewCurrent = true;
+ }
+ p.setOnPreferenceClickListener(this);
+ screen.addPreference(p);
+ }
+
+ if (!hasSetNewCurrent && currentKey != null && !prefs.isEmpty()) {
+ // Old "current" preference was removed, fallback to
+ // the first candidate
+ selectedPreference = prefs.get(0);
+ selectedPreference.setChecked(true);
+ onPreferenceClick(selectedPreference);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index ee0efbed3..2091ac69c 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -23,6 +23,9 @@ import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERE
import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
@@ -47,6 +50,8 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.lineage.LineageUtils;
+import com.android.launcher3.lineage.icon.IconPackStore;
+import com.android.launcher3.lineage.icon.IconPackSettingsActivity;
import com.android.launcher3.lineage.trust.TrustAppsActivity;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.SecureSettingsObserver;
@@ -71,6 +76,7 @@ public class SettingsActivity extends FragmentActivity
public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
public static final String KEY_TRUST_APPS = "pref_trust_apps";
+ public static final String KEY_ICON_PACK = "pref_icon_pack";
private static final String KEY_MINUS_ONE = "pref_enable_minus_one";
private static final String SEARCH_PACKAGE = "com.google.android.googlequicksearchbox";
@@ -132,7 +138,8 @@ public class SettingsActivity extends FragmentActivity
/**
* This fragment shows the launcher preferences.
*/
- public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
+ public static class LauncherSettingsFragment extends PreferenceFragmentCompat implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
private SecureSettingsObserver mNotificationDotsObserver;
@@ -154,6 +161,20 @@ public class SettingsActivity extends FragmentActivity
getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
+ updatePreferences();
+
+ Utilities.getPrefs(getContext())
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroyView () {
+ Utilities.getPrefs(getContext())
+ .unregisterOnSharedPreferenceChangeListener(this);
+ super.onDestroyView();
+ }
+
+ private void updatePreferences() {
PreferenceScreen screen = getPreferenceScreen();
for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
Preference preference = screen.getPreference(i);
@@ -169,6 +190,15 @@ public class SettingsActivity extends FragmentActivity
outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ switch (key) {
+ case IconPackStore.KEY_ICON_PACK:
+ updatePreferences();
+ break;
+ }
+ }
+
protected String getParentKeyForPref(String key) {
return null;
}
@@ -230,6 +260,9 @@ public class SettingsActivity extends FragmentActivity
return true;
});
return true;
+ case KEY_ICON_PACK:
+ setupIconPackPreference(preference);
+ return true;
}
return true;
@@ -283,5 +316,16 @@ public class SettingsActivity extends FragmentActivity
}
super.onDestroy();
}
+
+ private void setupIconPackPreference(Preference preference) {
+ final Context context = getContext();
+ final String defaultLabel = context.getString(R.string.icon_pack_default_label);
+ final String pkgLabel = new IconPackStore(context).getCurrentLabel(defaultLabel);
+ preference.setSummary(pkgLabel);
+ preference.setOnPreferenceClickListener(p -> {
+ startActivity(new Intent(getActivity(), IconPackSettingsActivity.class));
+ return true;
+ });
+ }
}
}
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 0f8152057..c42cef355 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -22,10 +22,14 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point;
import android.util.Log;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.lineage.icon.IconPackStore;
+
import java.util.function.Consumer;
/**
@@ -33,7 +37,8 @@ import java.util.function.Consumer;
* notifies the callback in case changes which affect the device profile occur.
*/
public class ConfigMonitor extends BroadcastReceiver implements
- DefaultDisplay.DisplayInfoChangeListener {
+ DefaultDisplay.DisplayInfoChangeListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "ConfigMonitor";
@@ -70,6 +75,7 @@ public class ConfigMonitor extends BroadcastReceiver implements
// Listen for configuration change
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+ Utilities.getPrefs(mContext).registerOnSharedPreferenceChangeListener(this);
}
@Override
@@ -102,6 +108,13 @@ public class ConfigMonitor extends BroadcastReceiver implements
}
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (IconPackStore.KEY_ICON_PACK.equals(key)) {
+ notifyChange();
+ }
+ }
+
private synchronized void notifyChange() {
if (mCallback != null) {
Consumer<Context> callback = mCallback;
@@ -115,6 +128,7 @@ public class ConfigMonitor extends BroadcastReceiver implements
mContext.unregisterReceiver(this);
DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext);
display.removeChangeListener(this);
+ Utilities.getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
} catch (Exception e) {
Log.e(TAG, "Failed to unregister config monitor", e);
}