summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoey Rizzoli <joey@lineageos.org>2017-11-12 12:40:39 +0100
committerArne Coucheron <arco68@gmail.com>2018-01-19 00:27:13 +0100
commitc6b85822544de5194cff97d7fe07236f5c7a5b32 (patch)
tree41e82255f6b126ee57a5f8c272e4aa6ac6d7740a
parent6a4b5ff6292360a34f6a6b33cb51f83d5ce82661 (diff)
downloadandroid_packages_apps_Trebuchet-c6b85822544de5194cff97d7fe07236f5c7a5b32.tar.gz
android_packages_apps_Trebuchet-c6b85822544de5194cff97d7fe07236f5c7a5b32.tar.bz2
android_packages_apps_Trebuchet-c6b85822544de5194cff97d7fe07236f5c7a5b32.zip
Trebuchet: icon packs support
Based on the following commits: * https://github.com/AOSPA/android_packages_apps_Launcher3/commit/cf4bde2098de8beec24a0c88bf2e51e19a9c22b6 * https://github.com/AOSPA/android_packages_apps_Launcher3/commit/8ddafe17ad6adc268ad1a93b7724d8b22028a71b * https://github.com/AOSPA/android_packages_apps_Launcher3/commit/0940176c3c01a1f87237811f42d05a592b0968b3 With the following additions: * Support for adaptive icons * UI changes * Cleanup and improvements Change-Id: I877a8b2dd204753179736fa7c1c34444e9b2c849 Signed-off-by: Joey Rizzoli <joey@lineageos.org>
-rw-r--r--AndroidManifest.xml4
-rw-r--r--res/drawable/ic_edit_app_no_shadow.xml10
-rw-r--r--res/drawable/ic_icon_change.xml30
-rw-r--r--res/drawable/ic_search.xml9
-rw-r--r--res/layout/all_icons_view.xml63
-rw-r--r--res/layout/all_icons_view_header.xml25
-rw-r--r--res/layout/target_edit_dialog.xml48
-rw-r--r--res/layout/target_edit_dialog_item.xml28
-rw-r--r--res/layout/target_edit_iconpack_chooser.xml52
-rw-r--r--res/menu/icon_picker.xml24
-rw-r--r--res/values-v26/lineageos_colors.xml20
-rw-r--r--res/values/config.xml2
-rw-r--r--res/values/lineage_colors.xml26
-rw-r--r--res/values/lineage_dimens.xml23
-rw-r--r--res/values/lineage_strings.xml13
-rw-r--r--res/values/lineage_styles.xml24
-rw-r--r--res/xml/launcher_preferences.xml4
-rw-r--r--src/com/android/launcher3/IconCache.java60
-rw-r--r--src/com/android/launcher3/Launcher.java106
-rw-r--r--src/com/android/launcher3/LauncherModel.java2
-rw-r--r--src/com/android/launcher3/SettingsActivity.java60
-rw-r--r--src/com/android/launcher3/Utilities.java16
-rw-r--r--src/com/android/launcher3/graphics/LauncherIcons.java5
-rw-r--r--src/com/android/launcher3/icons/CustomIconsProvider.java52
-rw-r--r--src/com/android/launcher3/icons/IconPickerActivity.java308
-rw-r--r--src/com/android/launcher3/icons/IconsHandler.java620
-rw-r--r--src/com/android/launcher3/icons/IconsSearchUtils.java47
-rw-r--r--src/com/android/launcher3/popup/PopupDataProvider.java1
-rw-r--r--src/com/android/launcher3/popup/SystemShortcut.java17
-rw-r--r--src/com/android/launcher3/util/SQLiteCacheHelper.java4
30 files changed, 1697 insertions, 6 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 61cb2a05b..2cd6bf396 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -165,5 +165,9 @@
-->
+ <activity android:name=".icons.IconPickerActivity"
+ android:label="@string/icon_pack_picker"
+ android:autoRemoveFromRecents="true"
+ android:theme="@android:style/Theme.DeviceDefault.Settings" />
</application>
</manifest>
diff --git a/res/drawable/ic_edit_app_no_shadow.xml b/res/drawable/ic_edit_app_no_shadow.xml
new file mode 100644
index 000000000..d713ffdab
--- /dev/null
+++ b/res/drawable/ic_edit_app_no_shadow.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:tint="?android:attr/textColorPrimary" >
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zM20.71,4.63l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z" />
+</vector>
diff --git a/res/drawable/ic_icon_change.xml b/res/drawable/ic_icon_change.xml
new file mode 100644
index 000000000..6495da6df
--- /dev/null
+++ b/res/drawable/ic_icon_change.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:start="31dp"
+ android:top="31dp">
+ <shape android:shape="oval">
+ <solid android:color="?android:colorAccent" />
+ </shape>
+ </item>
+ <item
+ android:start="31dp"
+ android:top="31dp">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <group
+ android:scaleX="0.75"
+ android:scaleY="0.75"
+ android:translateX="3"
+ android:translateY="3">
+ <path
+ android:fillColor="#aa000000"
+ android:pathData="M20.71,4.63L19.37,3.29C19,2.9 18.35,2.9 17.96,3.29L9,12.25L11.75,15L20.71,6.04C21.1,5.65 21.1,5 20.71,4.63M7,14A3,3 0 0,0 4,17C4,18.31 2.84,19 2,19C2.92,20.22 4.5,21 6,21A4,4 0 0,0 10,17A3,3 0 0,0 7,14Z" />
+ </group>
+ </vector>
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_search.xml b/res/drawable/ic_search.xml
new file mode 100644
index 000000000..affc7ba26
--- /dev/null
+++ b/res/drawable/ic_search.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
+</vector>
diff --git a/res/layout/all_icons_view.xml b/res/layout/all_icons_view.xml
new file mode 100644
index 000000000..f6707bc95
--- /dev/null
+++ b/res/layout/all_icons_view.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/icons_grid_name_header"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:background="@color/icon_picker_header_background"
+ android:elevation="6dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingEnd="16dp"
+ android:paddingStart="16dp">
+
+ <ImageView
+ android:id="@+id/icons_grid_name_header_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginEnd="16dp" />
+
+ <TextView
+ android:id="@+id/icons_grid_name_header_title"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:maxLines="1"
+ android:textAppearance="@android:style/TextAppearance.Material.Title"
+ android:textColor="@color/icon_picker_header_foreground" />
+ </LinearLayout>
+
+ <ProgressBar
+ android:id="@+id/icons_grid_progress"
+ style="?android:attr/progressBarStyle"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_centerInParent="true" />
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/icons_grid"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/icons_grid_name_header"
+ android:layout_centerHorizontal="true" />
+</RelativeLayout>
diff --git a/res/layout/all_icons_view_header.xml b/res/layout/all_icons_view_header.xml
new file mode 100644
index 000000000..ba15ff2dd
--- /dev/null
+++ b/res/layout/all_icons_view_header.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The LineageOS Projecy
+ 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:paddingStart="24dp"
+ android:paddingBottom="8dp"
+ android:paddingTop="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2" />
diff --git a/res/layout/target_edit_dialog.xml b/res/layout/target_edit_dialog.xml
new file mode 100644
index 000000000..da66a9c2c
--- /dev/null
+++ b/res/layout/target_edit_dialog.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="16dp">
+
+ <ImageView
+ android:id="@+id/edit_dialog_icon"
+ android:layout_width="@dimen/app_icon_size"
+ android:layout_height="@dimen/app_icon_size"
+ android:layout_gravity="bottom"
+ android:layout_marginEnd="16dp"
+ android:background="?android:selectableItemBackground"
+ android:foreground="@drawable/ic_icon_change"
+ android:scaleType="fitCenter"
+ tools:src="@mipmap/ic_launcher_home" />
+
+ <EditText
+ android:id="@+id/edit_dialog_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:ellipsize="end"
+ android:labelFor="@id/edit_dialog_icon"
+ android:maxLines="1"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:textColorPrimary"
+ android:textColorHint="?android:textColorSecondary"
+ tools:text="Trebuchet" />
+</LinearLayout>
diff --git a/res/layout/target_edit_dialog_item.xml b/res/layout/target_edit_dialog_item.xml
new file mode 100644
index 000000000..70492b565
--- /dev/null
+++ b/res/layout/target_edit_dialog_item.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:maxLines="1"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textSize="14sp" />
diff --git a/res/layout/target_edit_iconpack_chooser.xml b/res/layout/target_edit_iconpack_chooser.xml
new file mode 100644
index 000000000..a7c9fad3d
--- /dev/null
+++ b/res/layout/target_edit_iconpack_chooser.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingBottom="8dp"
+ android:paddingEnd="16dp"
+ android:paddingStart="16dp"
+ android:paddingTop="8dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@android:dimen/app_icon_size"
+ android:layout_height="@android:dimen/app_icon_size" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingEnd="8dp"
+ android:paddingStart="8dp"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textSize="18sp" />
+
+ <RadioButton
+ android:id="@+id/radio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:duplicateParentState="true"
+ android:focusable="false" />
+</LinearLayout>
diff --git a/res/menu/icon_picker.xml b/res/menu/icon_picker.xml
new file mode 100644
index 000000000..93d8506f2
--- /dev/null
+++ b/res/menu/icon_picker.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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/icon_picker_search"
+ android:showAsAction="ifRoom"
+ android:title="@string/icon_pack_picker_search"
+ android:icon="@drawable/ic_search"
+ android:actionViewClass="android.widget.SearchView" />
+</menu> \ No newline at end of file
diff --git a/res/values-v26/lineageos_colors.xml b/res/values-v26/lineageos_colors.xml
new file mode 100644
index 000000000..6eafd11e6
--- /dev/null
+++ b/res/values-v26/lineageos_colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+<resources>
+ <color name="icon_picker_header_background">#eeeeee</color>
+ <color name="icon_picker_header_foreground">@color/badge_color</color>
+</resources> \ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index d84ad3b15..f87d7a839 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -105,7 +105,7 @@
<string name="app_filter_class" translatable="false"></string>
<!-- Name of an icon provider class. -->
- <string name="icon_provider_class" translatable="false"></string>
+ <string name="icon_provider_class" translatable="false">com.android.launcher3.icons.CustomIconsProvider</string>
<!-- Name of a drawable factory class. -->
<string name="drawable_factory_class" translatable="false"></string>
diff --git a/res/values/lineage_colors.xml b/res/values/lineage_colors.xml
new file mode 100644
index 000000000..27cc41a82
--- /dev/null
+++ b/res/values/lineage_colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+<resources>
+ <color name="icon_settings_background">#333333</color>
+ <color name="icon_settings_text">#f5f5f5</color>
+
+ <color name="icon_picker_header_background">#37474f</color>
+ <color name="icon_picker_header_foreground">#ffffff</color>
+
+ <color name="dialog_background_light">#ffffff</color>
+ <color name="dialog_background_dark">#424242</color>
+</resources> \ No newline at end of file
diff --git a/res/values/lineage_dimens.xml b/res/values/lineage_dimens.xml
new file mode 100644
index 000000000..89d39c6e6
--- /dev/null
+++ b/res/values/lineage_dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+<resources>
+ <dimen name="app_icon_size">62dp</dimen>
+ <dimen name="icon_pack_icon_size">48dp</dimen>
+ <dimen name="icon_pack_vertical_margin">8dp</dimen>
+ <dimen name="edit_dialog_min_width">200dp</dimen>
+ <dimen name="grid_item_spacing">16dp</dimen>
+</resources> \ No newline at end of file
diff --git a/res/values/lineage_strings.xml b/res/values/lineage_strings.xml
index af71c9f64..8f8ac099c 100644
--- a/res/values/lineage_strings.xml
+++ b/res/values/lineage_strings.xml
@@ -40,4 +40,17 @@
<!-- Hide labels -->
<string name="desktop_show_labels">Show icon labels on desktop</string>
<string name="drawer_show_labels">Show icon labels in drawer</string>
+
+ <!-- Custom icons -->
+ <string name="app_edit_drop_target_label">Edit</string>
+ <string name="icon_pack_title">Icon pack</string>
+ <string name="icon_pack_picker">Select icon</string>
+ <string name="icon_pack_picker_search">Search icon</string>
+ <string name="icon_pack_suggestions">Suggestions</string>
+ <string name="icon_pack_all_icons">All icons</string>
+ <string name="icon_pack_reset">Reset</string>
+ <string name="icon_pack_system">System (Default)</string>
+
+ <!-- Default icon pack package name. Override to set a default icon pack, use "Default" to use system icons -->
+ <string name="icon_pack_default" translatable="false">Default</string>
</resources>
diff --git a/res/values/lineage_styles.xml b/res/values/lineage_styles.xml
new file mode 100644
index 000000000..330f235ff
--- /dev/null
+++ b/res/values/lineage_styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+<resources>
+ <style name="ChooseIconTheme" parent="android:style/Theme.DeviceDefault.Settings">
+ <item name="android:colorBackground">@color/icon_settings_background</item>
+ </style>
+
+ <style name="TextAppearance.Switch" parent="@android:style/TextAppearance.Material.Title" />
+
+</resources> \ No newline at end of file
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index f4663935d..22bd3e514 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -83,4 +83,8 @@
android:title="@string/drawer_show_labels"
android:defaultValue="true"
android:persistent="true" />
+
+ <Preference
+ android:key="pref_icon_pack"
+ android:title="@string/icon_pack_title" />
</PreferenceScreen>
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 573e8a256..cb58766bf 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -32,6 +32,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
@@ -45,6 +46,7 @@ import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.IconsHandler;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
@@ -85,6 +87,8 @@ public class IconCache {
public boolean isLowResIcon;
}
+ private static IconsHandler sIconsHandler;
+
private final HashMap<UserHandle, Bitmap> mDefaultIcons = new HashMap<>();
@Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@@ -378,6 +382,49 @@ public class IconCache {
addIconToDB(values, app.getComponentName(), info, userSerial);
}
+ public void flush() {
+ synchronized (mCache) {
+ mCache.clear();
+ }
+ }
+
+ CacheEntry getCacheEntry(LauncherActivityInfo app) {
+ final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
+ return mCache.get(key);
+ }
+
+ public void clearIconDataBase() {
+ mIconDb.clearDB();
+ }
+
+ public void addCustomInfoToDataBase(Drawable icon, ItemInfo info, CharSequence title) {
+ LauncherActivityInfo app = mLauncherApps.resolveActivity(info.getIntent(), info.user);
+ final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
+ CacheEntry entry = mCache.get(key);
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = mPackageManager.getPackageInfo(
+ app.getComponentName().getPackageName(), 0);
+ } catch (NameNotFoundException ignored) {
+ }
+ // We can't reuse the entry if the high-res icon is not present.
+ if (entry == null || entry.isLowResIcon || entry.icon == null) {
+ entry = new CacheEntry();
+ }
+ entry.icon = ((BitmapDrawable) icon).getBitmap();
+ entry.title = title != null ? title : app.getLabel();
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
+ mCache.put(key, entry);
+
+ Bitmap lowResIcon = generateLowResIcon(entry.icon);
+ ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(),
+ app.getApplicationInfo().packageName);
+ if (packageInfo != null) {
+ addIconToDB(values, app.getComponentName(), packageInfo,
+ mUserManager.getSerialNumberForUser(app.getUser()));
+ }
+ }
+
/**
* Updates {@param values} to contain versioning information and adds it to the DB.
* @param values {@link ContentValues} containing icon & title
@@ -595,6 +642,13 @@ public class IconCache {
return new ComponentKey(cn, user);
}
+ public static IconsHandler getIconsHandler(Context context) {
+ if (sIconsHandler == null) {
+ sIconsHandler = new IconsHandler(context);
+ }
+ return sIconsHandler;
+ }
+
/**
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
@@ -796,6 +850,12 @@ public class IconCache {
"PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
");");
}
+
+ private void clearDB() {
+ SQLiteDatabase db = getDb();
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreateTable(db);
+ }
}
private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ea5881608..108f6e5cc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -42,10 +42,13 @@ import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
@@ -62,6 +65,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.Log;
+import android.util.Pair;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -74,11 +78,17 @@ import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.OvershootInterpolator;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListPopupWindow;
import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
@@ -103,6 +113,9 @@ import com.android.launcher3.dynamicui.ExtractedColors;
import com.android.launcher3.dynamicui.WallpaperColorInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.IconPickerActivity;
+import com.android.launcher3.icons.IconsHandler;
import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.FileLog;
@@ -120,7 +133,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.ActivityResultInfo;
-import com.android.launcher3.util.RunnableWithId;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -128,6 +140,7 @@ import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.RunnableWithId;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Themes;
@@ -139,7 +152,6 @@ import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetsContainerView;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -351,6 +363,11 @@ public class Launcher extends BaseActivity
private PredictiveAppsProvider mPredictiveAppsProvider;
+ // Icons editor
+ private AlertDialog mIconEditDialog;
+ private EditText mIconEditTitle;
+ private IconsHandler mIconsHandler;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_STRICT_MODE) {
@@ -432,6 +449,8 @@ public class Launcher extends BaseActivity
lockAllApps();
+ mIconsHandler = IconCache.getIconsHandler(this);
+
restoreState(savedInstanceState);
if (LauncherAppState.PROFILE_STARTUP) {
@@ -1757,6 +1776,11 @@ public class Launcher extends BaseActivity
mWidgetsView.scrollToTop();
}
+ if (mIconEditDialog != null) {
+ mIconEditDialog.dismiss();
+ mIconEditDialog = null;
+ }
+
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent();
}
@@ -3925,6 +3949,84 @@ public class Launcher extends BaseActivity
mWorkspace.moveToDefaultScreen(false);
}
+ public void startEdit(ItemInfo info, ComponentName component) {
+ LauncherActivityInfo app = LauncherAppsCompat.getInstance(this)
+ .resolveActivity(info.getIntent(), info.user);
+ CharSequence label = mIconCache.getCacheEntry(app).title;
+
+ View dialogView = getLayoutInflater().inflate(R.layout.target_edit_dialog, null);
+ ImageView editIcon = (ImageView) dialogView.findViewById(R.id.edit_dialog_icon);
+ mIconEditTitle = (EditText) dialogView.findViewById(R.id.edit_dialog_title);
+ mIconEditTitle.setText(label);
+
+ Bitmap originalIcon = mIconsHandler.getDrawableIconForPackage(component);
+ Bitmap icon;
+ if (info instanceof ShortcutInfo) {
+ icon = ((ShortcutInfo) info).iconBitmap;
+ } else if (info instanceof AppInfo) {
+ icon = ((AppInfo) info).iconBitmap;
+ } else {
+ // Fallback to default icon
+ icon = originalIcon;
+ }
+ editIcon.setImageBitmap(icon);
+
+ Pair<List<String>, List<String>> iconPacks = mIconsHandler.getAllIconPacks();
+ ListPopupWindow listPopup = new ListPopupWindow(this);
+ listPopup.setAdapter(new ArrayAdapter<>(this, R.layout.target_edit_dialog_item,
+ iconPacks.second));
+ listPopup.setWidth(getResources().getDimensionPixelSize(R.dimen.edit_dialog_min_width));
+ listPopup.setAnchorView(editIcon);
+ listPopup.setModal(true);
+ listPopup.setOnItemClickListener(getIconPackClickListener(info, component, label,
+ iconPacks.first));
+
+ editIcon.setOnClickListener(v -> {
+ if (!iconPacks.second.isEmpty()) {
+ listPopup.show();
+ }
+ });
+
+ mIconEditDialog = new AlertDialog.Builder(this)
+ .setTitle(R.string.app_edit_drop_target_label)
+ .setView(dialogView)
+ .setOnDismissListener(dialog ->
+ LauncherAppState.getInstance(this).getModel().forceReload())
+ .setNeutralButton(R.string.icon_pack_reset, (dialog, which) ->
+ mIconCache.addCustomInfoToDataBase(new BitmapDrawable(getResources(),
+ originalIcon), info, null))
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ Drawable newIcon = new BitmapDrawable(getResources(), Utilities.ATLEAST_OREO ?
+ LauncherIcons.createIconBitmap(icon, getApplicationContext()) : icon);
+ mIconCache.addCustomInfoToDataBase(newIcon, info, mIconEditTitle.getText());
+ })
+ .create();
+
+ // Set background color
+ boolean isDark = Themes.getAttrBoolean(this, R.attr.isMainColorDark);
+ Window dialogWindow = mIconEditDialog.getWindow();
+ if (dialogWindow != null) {
+ dialogWindow.setBackgroundDrawableResource(isDark ?
+ R.color.dialog_background_dark : R.color.dialog_background_light);
+ }
+ mIconEditDialog.show();
+ }
+
+ private AdapterView.OnItemClickListener getIconPackClickListener(ItemInfo info,
+ ComponentName componentName,
+ CharSequence label,
+ List<String> packs) {
+ return (parent, view, postition, id) -> {
+ Intent intent = new Intent(Launcher.this, IconPickerActivity.class);
+ IconPickerActivity.setItemInfo(info);
+ intent.putExtra(IconPickerActivity.EXTRA_PACKAGE, componentName.getPackageName())
+ .putExtra(IconPickerActivity.EXTRA_LABEL, label)
+ .putExtra(IconPickerActivity.EXTRA_ICON_PACK, packs.get(postition));
+ Launcher.this.startActivity(intent);
+ mIconEditDialog.dismiss();
+ };
+ }
+
/**
* $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
*/
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index a906b00f1..d6810b6b7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -315,11 +315,13 @@ public class LauncherModel extends BroadcastReceiver
public void onPackageChanged(String packageName, UserHandle user) {
int op = PackageUpdatedTask.OP_UPDATE;
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
+ IconCache.getIconsHandler(mApp.getContext()).switchIconPacks(packageName);
}
@Override
public void onPackageRemoved(String packageName, UserHandle user) {
onPackagesRemoved(user, packageName);
+ IconCache.getIconsHandler(mApp.getContext()).switchIconPacks(packageName);
}
public void onPackagesRemoved(UserHandle user, String... packages) {
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 93fedf46d..5fe69f457 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -18,18 +18,21 @@ package com.android.launcher3;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -41,6 +44,7 @@ import android.view.View;
import android.widget.NumberPicker;
import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.icons.IconsHandler;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.SettingsObserver;
import com.android.launcher3.views.ButtonPreference;
@@ -63,6 +67,9 @@ public class SettingsActivity extends Activity {
private static final String KEY_SHOW_DESKTOP_LABELS = "pref_desktop_show_labels";
private static final String KEY_SHOW_DRAWER_LABELS = "pref_drawer_show_labels";
+ // Icon pack
+ public static final String KEY_ICON_PACK = "pref_icon_pack";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -90,6 +97,12 @@ public class SettingsActivity extends Activity {
private boolean mShouldRestart = false;
+ // Icon pack
+ private Preference mIconPackPref;
+ private String mDefaultIconPack;
+ private IconsHandler mIconsHandler;
+ private PackageManager mPackageManager;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -97,6 +110,8 @@ public class SettingsActivity extends Activity {
addPreferencesFromResource(R.xml.launcher_preferences);
ContentResolver resolver = getActivity().getContentResolver();
+ mPrefs = Utilities.getPrefs(getActivity().getApplicationContext());
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
// Setup allow rotation preference
Preference rotationPref = findPreference(Utilities.ALLOW_ROTATION_PREFERENCE_KEY);
@@ -151,6 +166,16 @@ public class SettingsActivity extends Activity {
mPrefs = Utilities.getPrefs(getActivity().getApplicationContext());
mPrefs.registerOnSharedPreferenceChangeListener(this);
+
+ mIconPackPref = findPreference(KEY_ICON_PACK);
+ mIconPackPref.setOnPreferenceClickListener(preference -> {
+ mIconsHandler.showDialog(getActivity());
+ return true;
+ });
+ mPackageManager = getActivity().getPackageManager();
+ mDefaultIconPack = mPrefs.getString(KEY_ICON_PACK, getString(R.string.icon_pack_default));
+ mIconsHandler = IconCache.getIconsHandler(getActivity().getApplicationContext());
+ updateIconPackEntry();
}
@Override
@@ -178,6 +203,9 @@ public class SettingsActivity extends Activity {
mGridPref.setSummary(mPrefs.getString(KEY_GRID_SIZE, getDefaulGridSize()));
mShouldRestart = true;
break;
+ case KEY_ICON_PACK:
+ updateIconPackEntry();
+ break;
case KEY_SHOW_DESKTOP_LABELS:
case KEY_SHOW_DRAWER_LABELS:
mShouldRestart = true;
@@ -185,6 +213,12 @@ public class SettingsActivity extends Activity {
}
}
+ @Override
+ public void onPause() {
+ super.onPause();
+ mIconsHandler.hideDialog();
+ }
+
private void setCustomGridSize() {
int minValue = 3;
int maxValue = 9;
@@ -222,11 +256,33 @@ public class SettingsActivity extends Activity {
.show();
}
+
private String getDefaulGridSize() {
InvariantDeviceProfile profile = new InvariantDeviceProfile(getActivity());
return Utilities.getGridValue(profile.numColumns, profile.numRows);
}
+ private void updateIconPackEntry() {
+ ApplicationInfo info = null;
+ String iconPack = mPrefs.getString(KEY_ICON_PACK, mDefaultIconPack);
+ String summary = getString(R.string.icon_pack_system);
+ Drawable icon = getResources().getDrawable(android.R.mipmap.sym_def_app_icon);
+
+ if (!mIconsHandler.isDefaultIconPack()) {
+ try {
+ info = mPackageManager.getApplicationInfo(iconPack, PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ if (info != null) {
+ summary = mPackageManager.getApplicationLabel(info).toString();
+ icon = mPackageManager.getApplicationIcon(info);
+ }
+ }
+
+ mIconPackPref.setSummary(summary);
+ mIconPackPref.setIcon(icon);
+ }
+
private void triggerRestart() {
Context context = getActivity().getApplicationContext();
Intent intent = new Intent(Intent.ACTION_MAIN);
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d2421f6c4..cab693d80 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -51,6 +51,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.CustomIconsProvider;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -631,6 +632,11 @@ public final class Utilities {
}
public static <T> T getOverrideObject(Class<T> clazz, Context context, int resId) {
+ if (R.string.icon_provider_class == resId &&
+ clazz.getSimpleName().equals(IconProvider.class.getSimpleName())) {
+ return (T) new CustomIconsProvider(context);
+ }
+
String className = context.getString(resId);
if (!TextUtils.isEmpty(className)) {
try {
@@ -683,4 +689,14 @@ public final class Utilities {
return String.format(Locale.ENGLISH, "%1$d%2$s%3$d", columns,
GRID_VALUE_SEPARATOR, rows);
}
+
+ public static boolean isNotUsingIconPack(Context context) {
+ SharedPreferences prefs = Utilities.getPrefs(context.getApplicationContext());
+ String defaultPack = context.getString(R.string.icon_pack_default);
+ String defaultLocalziedPack = context.getString(R.string.icon_pack_system);
+ String currentPack = prefs.getString(SettingsActivity.KEY_ICON_PACK, defaultPack);
+
+ return !currentPack.equals(defaultPack) && !currentPack.equals(defaultLocalziedPack);
+ }
+
}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index d55baf0f8..7ec53f0be 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -42,6 +43,7 @@ import com.android.launcher3.FastBitmapDrawable;
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.config.FeatureFlags;
import com.android.launcher3.model.PackageItemInfo;
@@ -104,7 +106,8 @@ public class LauncherIcons {
float scale = 1f;
if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
normalizer = IconNormalizer.getInstance(context);
- if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+ if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O &&
+ Utilities.isNotUsingIconPack(context)) {
boolean[] outShape = new boolean[1];
AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
diff --git a/src/com/android/launcher3/icons/CustomIconsProvider.java b/src/com/android/launcher3/icons/CustomIconsProvider.java
new file mode 100644
index 000000000..1cdb208ad
--- /dev/null
+++ b/src/com/android/launcher3/icons/CustomIconsProvider.java
@@ -0,0 +1,52 @@
+/*
+ * 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.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.IconProvider;
+import com.android.launcher3.Utilities;
+
+public class CustomIconsProvider extends IconProvider {
+ private Context mContext;
+ private IconsHandler mHandler;
+
+ public CustomIconsProvider(Context context) {
+ super();
+ mContext = context;
+ mHandler = IconCache.getIconsHandler(context);
+ }
+
+ @Override
+ public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) {
+ if (Utilities.ATLEAST_OREO && !Utilities.isNotUsingIconPack(mContext)) {
+ return mContext.getPackageManager().getApplicationIcon(info.getApplicationInfo());
+ }
+
+ Bitmap bm = mHandler.getDrawableIconForPackage(info.getComponentName());
+ if (bm == null) {
+ return info.getIcon(iconDpi);
+ }
+
+ return new BitmapDrawable(mContext.getResources(), bm);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/icons/IconPickerActivity.java b/src/com/android/launcher3/icons/IconPickerActivity.java
new file mode 100644
index 000000000..3b49871cb
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconPickerActivity.java
@@ -0,0 +1,308 @@
+/*
+ * 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.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class IconPickerActivity extends Activity {
+ private static final String TAG = "IconPickerActivity";
+ public static final String EXTRA_PACKAGE = "app_package";
+ public static final String EXTRA_LABEL = "app_label";
+ public static final String EXTRA_ICON_PACK = "icon_pack_package";
+
+ private GridLayoutManager mGridLayout;
+ private ProgressBar mProgressBar;
+ private RecyclerView mIconsGrid;
+
+ private static ItemInfo sItemInfo;
+ private IconCache mIconCache;
+ private IconsHandler mIconsHandler;
+
+ private List<String> mAllicons;
+ private List<String> mMatchingIcons;
+ private GridAdapter mAdapter;
+
+ private String mCurrentPackageLabel;
+ private String mCurrentPackageName;
+ private String mIconPackPackageName;
+ private int mIconSize;
+ private boolean mCanSearch;
+
+ public static void setItemInfo(ItemInfo info) {
+ sItemInfo = info;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.all_icons_view);
+
+ mCurrentPackageName = getIntent().getStringExtra(EXTRA_PACKAGE);
+ mCurrentPackageLabel = getIntent().getStringExtra(EXTRA_LABEL);
+ mIconPackPackageName = getIntent().getStringExtra(EXTRA_ICON_PACK);
+
+ mIconCache = LauncherAppState.getInstance(this).getIconCache();
+ mIconsHandler = IconCache.getIconsHandler(this);
+
+ int itemSpacing = getResources().getDimensionPixelSize(R.dimen.grid_item_spacing);
+ mIconsGrid = (RecyclerView) findViewById(R.id.icons_grid);
+ mIconsGrid.setHasFixedSize(true);
+ mIconsGrid.addItemDecoration(new GridItemSpacer(itemSpacing));
+ mGridLayout = new GridLayoutManager(this, 4);
+ mIconsGrid.setLayoutManager(mGridLayout);
+ mIconsGrid.setAlpha(0.0f);
+
+ mProgressBar = (ProgressBar) findViewById(R.id.icons_grid_progress);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.icon_pack_icon_size);
+
+ LinearLayout headerBar = (LinearLayout) findViewById(R.id.icons_grid_name_header);
+ try {
+ PackageManager pm = getPackageManager();
+ ApplicationInfo info = pm.getApplicationInfo(mIconPackPackageName,
+ PackageManager.GET_META_DATA);
+
+ TextView headerTitle = (TextView) findViewById(R.id.icons_grid_name_header_title);
+ ImageView headerIcon = (ImageView) findViewById(R.id.icons_grid_name_header_icon);
+
+ headerTitle.setText(pm.getApplicationLabel(info));
+ headerIcon.setImageDrawable(pm.getApplicationIcon(info));
+ } catch (PackageManager.NameNotFoundException e) {
+ headerBar.setVisibility(View.GONE);
+ Log.e(TAG, e.getMessage());
+ }
+
+ new Thread(() -> {
+ mCanSearch = false;
+ final Activity activity = IconPickerActivity.this;
+ mAllicons = mIconsHandler.getAllDrawables(mIconPackPackageName);
+ mMatchingIcons = mIconsHandler.getMatchingDrawables(mCurrentPackageName);
+ activity.runOnUiThread(() -> {
+ mAdapter = new GridAdapter(mAllicons, mMatchingIcons);
+ mIconsGrid.setAdapter(mAdapter);
+ mProgressBar.setVisibility(View.GONE);
+ mIconsGrid.animate().alpha(1.0f);
+ mCanSearch = true;
+ });
+ }).start();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.icon_picker, menu);
+
+ MenuItem search = menu.findItem(R.id.icon_picker_search);
+ SearchView searchView = (SearchView) search.getActionView();
+ searchView.setOnQueryTextListener(getSearchListener());
+ return true;
+ }
+
+ private SearchView.OnQueryTextListener getSearchListener() {
+ return new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String s) {
+ if (!mCanSearch) {
+ return false;
+ }
+ IconsSearchUtils.filter(s, mMatchingIcons, mAllicons, mAdapter);
+ return true;
+ }
+ };
+ }
+
+ class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> implements Filterable {
+
+ private static final int TYPE_MATCHING_HEADER = 0;
+ private static final int TYPE_MATCHING_ICONS = 1;
+ private static final int TYPE_ALL_HEADER = 2;
+ private static final int TYPE_ALL_ICONS = 3;
+
+ private boolean mNoMatchingDrawables;
+
+ private List<String> mAllDrawables = new ArrayList<>();
+ private List<String> mMatchingDrawables = new ArrayList<>();
+ private final SpanSizeLookup mSpanSizeLookup = new SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return getItemViewType(position) == TYPE_MATCHING_HEADER ||
+ getItemViewType(position) == TYPE_ALL_HEADER ? 4 : 1;
+ }
+ };
+
+ private GridAdapter(List<String> allDrawables, List<String> matchingDrawables) {
+ mAllDrawables.add(null);
+ mAllDrawables.addAll(allDrawables);
+ mMatchingDrawables.add(null);
+ mMatchingDrawables.addAll(matchingDrawables);
+ mGridLayout.setSpanSizeLookup(mSpanSizeLookup);
+ mNoMatchingDrawables = matchingDrawables.isEmpty();
+ if (mNoMatchingDrawables) {
+ mMatchingDrawables.clear();
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (!mNoMatchingDrawables && position < mMatchingDrawables.size() &&
+ mMatchingDrawables.get(position) == null) {
+ return TYPE_MATCHING_HEADER;
+ }
+
+ if (!mNoMatchingDrawables && position > TYPE_MATCHING_HEADER &&
+ position < mMatchingDrawables.size()) {
+ return TYPE_MATCHING_ICONS;
+ }
+
+ if (position == mMatchingDrawables.size()) {
+ return TYPE_ALL_HEADER;
+ }
+ return TYPE_ALL_ICONS;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAllDrawables.size() + 1;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ Activity activity = IconPickerActivity.this;
+ if (viewType == TYPE_MATCHING_HEADER) {
+ TextView text = (TextView) activity.getLayoutInflater().inflate(
+ R.layout.all_icons_view_header, null);
+ text.setText(R.string.icon_pack_suggestions);
+ return new ViewHolder(text);
+ }
+ if (viewType == TYPE_ALL_HEADER) {
+ TextView text = (TextView) activity.getLayoutInflater().inflate(
+ R.layout.all_icons_view_header, null);
+ text.setText(R.string.icon_pack_all_icons);
+ return new ViewHolder(text);
+ }
+
+ ImageView view = new ImageView(activity);
+ RecyclerView.LayoutParams params = new RecyclerView.LayoutParams(
+ RecyclerView.LayoutParams.MATCH_PARENT, mIconSize);
+ view.setLayoutParams(params);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ if (holder.getItemViewType() != TYPE_MATCHING_HEADER
+ && holder.getItemViewType() != TYPE_ALL_HEADER) {
+ boolean drawablesMatching = holder.getItemViewType() == TYPE_MATCHING_ICONS;
+ final List<String> drawables = drawablesMatching ?
+ mMatchingDrawables : mAllDrawables;
+ if (holder.getAdapterPosition() >= drawables.size()) {
+ return;
+ }
+ holder.itemView.setOnClickListener(v -> {
+ Drawable icon = mIconsHandler.loadDrawable(mIconPackPackageName,
+ drawables.get(holder.getAdapterPosition()), true);
+ if (icon != null) {
+ mIconCache.addCustomInfoToDataBase(icon, sItemInfo, mCurrentPackageLabel);
+ }
+ IconPickerActivity.this.finish();
+ });
+ Drawable icon = null;
+ String drawable = drawables.get(position);
+ try {
+ icon = mIconsHandler.loadDrawable(mIconPackPackageName, drawable, true);
+ } catch (OutOfMemoryError e) {
+ Log.e(TAG, e.getMessage());
+ }
+ if (icon != null) {
+ ((ImageView) holder.itemView).setImageDrawable(icon);
+ }
+ }
+ }
+
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ protected FilterResults performFiltering(CharSequence charSequence) {
+ return new FilterResults();
+ }
+
+ @Override
+ protected void publishResults(CharSequence charSequence, FilterResults results) {
+ }
+ };
+ }
+
+
+ void filterList(List<String> filteredDrawables, List<String> filteredMatches) {
+ mAllDrawables = filteredDrawables;
+ mMatchingDrawables = filteredMatches;
+ notifyDataSetChanged();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ private ViewHolder(View v) {
+ super(v);
+ }
+ }
+ }
+
+ private class GridItemSpacer extends RecyclerView.ItemDecoration {
+ private int spacing;
+
+ private GridItemSpacer(int spacing) {
+ this.spacing = spacing;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.top = spacing;
+ }
+ }
+} \ No newline at end of file
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
diff --git a/src/com/android/launcher3/icons/IconsSearchUtils.java b/src/com/android/launcher3/icons/IconsSearchUtils.java
new file mode 100644
index 000000000..b9e2d4155
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconsSearchUtils.java
@@ -0,0 +1,47 @@
+package com.android.launcher3.icons;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class IconsSearchUtils {
+ static void filter(String query, List<String> matchingIcons, List<String> allIcons,
+ IconPickerActivity.GridAdapter mGridAdapter) {
+
+ //new array list that will hold the filtered data
+ List<String> resultsFromAllIcons = new ArrayList<>();
+ List<String> resultsFromMatchingIcons = new ArrayList<>();
+ boolean noMatch = matchingIcons.isEmpty();
+
+ if (query.isEmpty()) {
+ resultsFromAllIcons.clear();
+ resultsFromAllIcons.add(null);
+ resultsFromAllIcons.addAll(allIcons);
+ resultsFromMatchingIcons.clear();
+ if (!noMatch) {
+ resultsFromMatchingIcons.add(null);
+ resultsFromMatchingIcons.addAll(matchingIcons);
+ }
+ mGridAdapter.filterList(resultsFromAllIcons, resultsFromMatchingIcons);
+ } else {
+ resultsFromAllIcons.clear();
+ resultsFromMatchingIcons.clear();
+ if (noMatch) {
+ getFilteredResults(allIcons, resultsFromAllIcons, query);
+ } else {
+ resultsFromAllIcons.clear();
+ resultsFromMatchingIcons.clear();
+ getFilteredResults(allIcons, resultsFromAllIcons, query);
+ getFilteredResults(matchingIcons, resultsFromMatchingIcons, query);
+ }
+ //calling a method of the adapter class and passing the filtered list
+ mGridAdapter.filterList(resultsFromAllIcons, resultsFromMatchingIcons);
+ }
+ }
+
+ private static void getFilteredResults(List<String> originalList, List<String> filteredResults,
+ String query) {
+ for (String item : originalList) {
+ if (item.contains(query)) filteredResults.add(item);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index de9f25e12..d31124fa6 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -53,6 +53,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] {
new SystemShortcut.AppInfo(),
new SystemShortcut.Widgets(),
+ new SystemShortcut.AppEdit()
};
private final Launcher mLauncher;
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 6254d2d00..587c6682d 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,5 +1,6 @@
package com.android.launcher3.popup;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -95,4 +96,20 @@ public abstract class SystemShortcut extends ItemInfo {
};
}
}
+
+ public static class AppEdit extends SystemShortcut {
+ AppEdit() {
+ super(R.drawable.ic_edit_app_no_shadow, R.string.app_edit_drop_target_label);
+ }
+
+ @Override
+ public View.OnClickListener getOnClickListener(final Launcher launcher,
+ final ItemInfo itemInfo) {
+ return view -> {
+ ComponentName componentName = itemInfo.getTargetComponent();
+ launcher.startEdit(itemInfo, componentName);
+ };
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index 9084bfbd3..8ba4dffda 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -89,6 +89,10 @@ public abstract class SQLiteCacheHelper {
protected abstract void onCreateTable(SQLiteDatabase db);
+ public SQLiteDatabase getDb() {
+ return mOpenHelper.getWritableDatabase();
+ }
+
/**
* A private inner class to prevent direct DB access.
*/