diff options
author | Joey Rizzoli <joey@lineageos.org> | 2017-11-12 12:40:39 +0100 |
---|---|---|
committer | Arne Coucheron <arco68@gmail.com> | 2018-01-19 00:27:13 +0100 |
commit | c6b85822544de5194cff97d7fe07236f5c7a5b32 (patch) | |
tree | 41e82255f6b126ee57a5f8c272e4aa6ac6d7740a | |
parent | 6a4b5ff6292360a34f6a6b33cb51f83d5ce82661 (diff) | |
download | android_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>
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. */ |