From bcd90a38b28f7c57bce8057d855e4c276aab2776 Mon Sep 17 00:00:00 2001 From: Clark Scheff Date: Sun, 28 Aug 2016 09:21:05 -0700 Subject: Theme chooser for the people Let freedom ring! Change-Id: I12b3e6b5d46eb2e13afd841dfd5c215af64188d8 TICKET: OSS-67 --- Android.mk | 25 +- AndroidManifest.xml | 34 +- res/color/popup_menu_text_color.xml | 18 +- res/drawable/apply_cancel_bg.xml | 18 +- res/drawable/apply_circle_bg.xml | 18 +- res/drawable/apply_progress_background.xml | 18 +- res/drawable/apply_progress_bar.xml | 18 +- res/drawable/apply_progress_indicator.xml | 18 +- res/drawable/bg_grid.xml | 18 +- res/drawable/btn_quicktheme_remove_hover.xml | 17 + res/drawable/btn_quicktheme_remove_normal.xml | 17 + res/drawable/component_selection_selector.xml | 18 +- res/drawable/component_selector_divider.xml | 16 + res/drawable/ic_action_reset.xml | 18 +- res/drawable/ic_appthemer.xml | 24 +- res/drawable/ic_qs_appthemer.xml | 24 +- res/drawable/ic_themestore.xml | 24 +- res/drawable/per_app_theme_list_background.xml | 17 + res/drawable/quick_theme_fab.xml | 24 +- res/drawable/save_apply_button_selector.xml | 18 +- res/drawable/shop_themes_bg.xml | 18 +- res/drawable/theme_component_bg_transition.xml | 18 +- res/drawable/wallpaper_border.xml | 18 +- res/drawable/wallpaper_none_bg.xml | 18 +- res/layout/activity_main.xml | 22 +- res/layout/audible_card.xml | 26 +- res/layout/bootani_component_selection_item.xml | 20 +- res/layout/bootanim_card.xml | 26 +- res/layout/bottom_actions.xml | 24 +- res/layout/component_selection_pager_item.xml | 18 +- .../component_selection_sounds_pager_item.xml | 18 +- res/layout/component_selector.xml | 28 +- res/layout/confirm_cancel_overlay.xml | 26 +- res/layout/customize_reset_theme_layout.xml | 22 +- res/layout/font_card.xml | 26 +- res/layout/font_component_selection_item.xml | 22 +- res/layout/fragment_pager_list.xml | 32 +- res/layout/icon_card.xml | 24 +- res/layout/icon_component_selection_item.xml | 20 +- res/layout/navbar_card.xml | 24 +- .../navigation_bar_component_selection_item.xml | 20 +- res/layout/per_app_delete_box_window.xml | 17 + res/layout/per_app_fab_floating_window_icon.xml | 17 + res/layout/per_app_theme_list.xml | 23 +- res/layout/per_app_theme_list_item.xml | 17 + res/layout/processing_layout.xml | 20 +- res/layout/save_apply_button.xml | 20 +- res/layout/sound_component_selection_item.xml | 20 +- res/layout/status_bar_card.xml | 24 +- res/layout/status_bar_component_selection_item.xml | 20 +- res/layout/style_card.xml | 24 +- res/layout/tag_applied.xml | 18 +- res/layout/tag_customized.xml | 20 +- res/layout/tag_default.xml | 20 +- res/layout/tag_legacy.xml | 20 +- res/layout/tag_updated.xml | 20 +- res/layout/theme_tags.xml | 22 +- res/layout/wallpaper_card.xml | 22 +- res/layout/wallpaper_component_selection_item.xml | 24 +- res/menu/overflow.xml | 18 +- res/values/attrs.xml | 18 +- res/values/colors.xml | 18 +- res/values/dimens.xml | 18 +- res/values/ids.xml | 17 + res/values/integers.xml | 18 +- res/values/strings.xml | 22 +- res/values/styles.xml | 18 +- src/android/support/v4/view/ThemeViewPager.java | 18 +- .../widget/ComponentSelectorLinearLayout.java | 16 +- src/com/cyngn/theme/chooser/AppReceiver.java | 59 - src/com/cyngn/theme/chooser/BootReceiver.java | 42 - src/com/cyngn/theme/chooser/ChooserActivity.java | 1191 -------- src/com/cyngn/theme/chooser/ComponentCardView.java | 219 -- src/com/cyngn/theme/chooser/ComponentSelector.java | 920 ------ .../theme/chooser/IconTransitionDrawable.java | 171 -- src/com/cyngn/theme/chooser/MyThemeFragment.java | 693 ----- .../chooser/NewFragmentStatePagerAdapter.java | 332 --- .../chooser/NotificationHijackingService.java | 74 - src/com/cyngn/theme/chooser/PagerContainer.java | 226 -- src/com/cyngn/theme/chooser/ThemeFragment.java | 3045 ------------------- src/com/cyngn/theme/chooser/WallpaperCardView.java | 146 - .../theme/perapptheming/PerAppThemeListLayout.java | 160 - .../theme/perapptheming/PerAppThemeListView.java | 59 - .../theme/perapptheming/PerAppThemingWindow.java | 1064 ------- src/com/cyngn/theme/util/AudioUtils.java | 92 - src/com/cyngn/theme/util/BootAnimationHelper.java | 305 -- src/com/cyngn/theme/util/CursorLoaderHelper.java | 433 --- src/com/cyngn/theme/util/FontConfigParser.java | 161 -- src/com/cyngn/theme/util/IconPreviewHelper.java | 183 -- src/com/cyngn/theme/util/NotificationHelper.java | 82 - src/com/cyngn/theme/util/PreferenceUtils.java | 119 - src/com/cyngn/theme/util/ThemedTypefaceHelper.java | 127 - src/com/cyngn/theme/util/TypefaceHelperCache.java | 43 - src/com/cyngn/theme/util/Utils.java | 710 ----- src/com/cyngn/theme/util/WallpaperUtils.java | 426 --- .../theme/widget/AutoSnapHorizontalScrollView.java | 154 - src/com/cyngn/theme/widget/BootAniImageView.java | 210 -- .../cyngn/theme/widget/ConfirmCancelOverlay.java | 74 - src/com/cyngn/theme/widget/FittedTextView.java | 84 - src/com/cyngn/theme/widget/LatoTextView.java | 170 -- src/com/cyngn/theme/widget/LockableScrollView.java | 55 - src/com/cyngn/theme/widget/NavBarSpace.java | 39 - src/com/cyngn/theme/widget/ThemeTagLayout.java | 125 - .../viewpagerindicator/CirclePageIndicator.java | 2 +- src/org/cyanogenmod/theme/chooser/AppReceiver.java | 73 + .../cyanogenmod/theme/chooser/BootReceiver.java | 56 + .../cyanogenmod/theme/chooser/ChooserActivity.java | 1191 ++++++++ .../theme/chooser/ComponentCardView.java | 233 ++ .../theme/chooser/ComponentSelector.java | 934 ++++++ .../theme/chooser/IconTransitionDrawable.java | 171 ++ .../cyanogenmod/theme/chooser/MyThemeFragment.java | 707 +++++ .../chooser/NewFragmentStatePagerAdapter.java | 333 +++ .../chooser/NotificationHijackingService.java | 88 + .../cyanogenmod/theme/chooser/PagerContainer.java | 219 ++ .../cyanogenmod/theme/chooser/ThemeFragment.java | 3059 ++++++++++++++++++++ .../theme/chooser/WallpaperCardView.java | 160 + .../theme/perapptheming/PerAppThemeListLayout.java | 174 ++ .../theme/perapptheming/PerAppThemeListView.java | 74 + .../theme/perapptheming/PerAppThemingWindow.java | 1078 +++++++ src/org/cyanogenmod/theme/util/AudioUtils.java | 106 + .../theme/util/BootAnimationHelper.java | 319 ++ .../cyanogenmod/theme/util/CursorLoaderHelper.java | 447 +++ .../cyanogenmod/theme/util/FontConfigParser.java | 175 ++ .../cyanogenmod/theme/util/IconPreviewHelper.java | 197 ++ .../cyanogenmod/theme/util/NotificationHelper.java | 96 + .../cyanogenmod/theme/util/PreferenceUtils.java | 133 + .../theme/util/ThemedTypefaceHelper.java | 141 + .../theme/util/TypefaceHelperCache.java | 57 + src/org/cyanogenmod/theme/util/Utils.java | 724 +++++ src/org/cyanogenmod/theme/util/WallpaperUtils.java | 440 +++ .../theme/widget/AutoSnapHorizontalScrollView.java | 168 ++ .../cyanogenmod/theme/widget/BootAniImageView.java | 226 ++ .../theme/widget/ConfirmCancelOverlay.java | 89 + .../cyanogenmod/theme/widget/FittedTextView.java | 98 + src/org/cyanogenmod/theme/widget/LatoTextView.java | 184 ++ .../theme/widget/LockableScrollView.java | 69 + src/org/cyanogenmod/theme/widget/NavBarSpace.java | 53 + .../cyanogenmod/theme/widget/ThemeTagLayout.java | 140 + 138 files changed, 13598 insertions(+), 12230 deletions(-) delete mode 100644 src/com/cyngn/theme/chooser/AppReceiver.java delete mode 100644 src/com/cyngn/theme/chooser/BootReceiver.java delete mode 100644 src/com/cyngn/theme/chooser/ChooserActivity.java delete mode 100644 src/com/cyngn/theme/chooser/ComponentCardView.java delete mode 100644 src/com/cyngn/theme/chooser/ComponentSelector.java delete mode 100644 src/com/cyngn/theme/chooser/IconTransitionDrawable.java delete mode 100644 src/com/cyngn/theme/chooser/MyThemeFragment.java delete mode 100644 src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java delete mode 100644 src/com/cyngn/theme/chooser/NotificationHijackingService.java delete mode 100644 src/com/cyngn/theme/chooser/PagerContainer.java delete mode 100644 src/com/cyngn/theme/chooser/ThemeFragment.java delete mode 100644 src/com/cyngn/theme/chooser/WallpaperCardView.java delete mode 100644 src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java delete mode 100644 src/com/cyngn/theme/perapptheming/PerAppThemeListView.java delete mode 100644 src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java delete mode 100644 src/com/cyngn/theme/util/AudioUtils.java delete mode 100644 src/com/cyngn/theme/util/BootAnimationHelper.java delete mode 100644 src/com/cyngn/theme/util/CursorLoaderHelper.java delete mode 100644 src/com/cyngn/theme/util/FontConfigParser.java delete mode 100644 src/com/cyngn/theme/util/IconPreviewHelper.java delete mode 100644 src/com/cyngn/theme/util/NotificationHelper.java delete mode 100644 src/com/cyngn/theme/util/PreferenceUtils.java delete mode 100644 src/com/cyngn/theme/util/ThemedTypefaceHelper.java delete mode 100644 src/com/cyngn/theme/util/TypefaceHelperCache.java delete mode 100644 src/com/cyngn/theme/util/Utils.java delete mode 100644 src/com/cyngn/theme/util/WallpaperUtils.java delete mode 100644 src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java delete mode 100644 src/com/cyngn/theme/widget/BootAniImageView.java delete mode 100644 src/com/cyngn/theme/widget/ConfirmCancelOverlay.java delete mode 100644 src/com/cyngn/theme/widget/FittedTextView.java delete mode 100644 src/com/cyngn/theme/widget/LatoTextView.java delete mode 100644 src/com/cyngn/theme/widget/LockableScrollView.java delete mode 100644 src/com/cyngn/theme/widget/NavBarSpace.java delete mode 100644 src/com/cyngn/theme/widget/ThemeTagLayout.java create mode 100644 src/org/cyanogenmod/theme/chooser/AppReceiver.java create mode 100644 src/org/cyanogenmod/theme/chooser/BootReceiver.java create mode 100644 src/org/cyanogenmod/theme/chooser/ChooserActivity.java create mode 100644 src/org/cyanogenmod/theme/chooser/ComponentCardView.java create mode 100644 src/org/cyanogenmod/theme/chooser/ComponentSelector.java create mode 100644 src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java create mode 100644 src/org/cyanogenmod/theme/chooser/MyThemeFragment.java create mode 100644 src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java create mode 100644 src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java create mode 100644 src/org/cyanogenmod/theme/chooser/PagerContainer.java create mode 100644 src/org/cyanogenmod/theme/chooser/ThemeFragment.java create mode 100644 src/org/cyanogenmod/theme/chooser/WallpaperCardView.java create mode 100644 src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java create mode 100644 src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java create mode 100644 src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java create mode 100644 src/org/cyanogenmod/theme/util/AudioUtils.java create mode 100644 src/org/cyanogenmod/theme/util/BootAnimationHelper.java create mode 100644 src/org/cyanogenmod/theme/util/CursorLoaderHelper.java create mode 100644 src/org/cyanogenmod/theme/util/FontConfigParser.java create mode 100644 src/org/cyanogenmod/theme/util/IconPreviewHelper.java create mode 100644 src/org/cyanogenmod/theme/util/NotificationHelper.java create mode 100644 src/org/cyanogenmod/theme/util/PreferenceUtils.java create mode 100644 src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java create mode 100644 src/org/cyanogenmod/theme/util/TypefaceHelperCache.java create mode 100644 src/org/cyanogenmod/theme/util/Utils.java create mode 100644 src/org/cyanogenmod/theme/util/WallpaperUtils.java create mode 100644 src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java create mode 100644 src/org/cyanogenmod/theme/widget/BootAniImageView.java create mode 100644 src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java create mode 100644 src/org/cyanogenmod/theme/widget/FittedTextView.java create mode 100644 src/org/cyanogenmod/theme/widget/LatoTextView.java create mode 100644 src/org/cyanogenmod/theme/widget/LockableScrollView.java create mode 100644 src/org/cyanogenmod/theme/widget/NavBarSpace.java create mode 100644 src/org/cyanogenmod/theme/widget/ThemeTagLayout.java diff --git a/Android.mk b/Android.mk index 2878cf7..362622e 100644 --- a/Android.mk +++ b/Android.mk @@ -1,3 +1,20 @@ +# +# Copyright (C) 2016 Cyanogen, Inc. +# Copyright (C) 2016 The CyanogenMod 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. +# + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) @@ -5,15 +22,9 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_MODULE_TAGS := optional -# Obfuscate user builds, eng and userdebug will remain unobfuscated -ifeq ($(TARGET_BUILD_VARIANT),user) -LOCAL_PROGUARD_ENABLED := obfuscation -endif - LOCAL_PROGUARD_FLAG_FILES := proguard.flags -LOCAL_OVERRIDES_PACKAGES := ThemeChooser -LOCAL_PACKAGE_NAME := ModThemeChooser +LOCAL_PACKAGE_NAME := ThemeChooser LOCAL_CERTIFICATE := platform LOCAL_PRIVILEGED_MODULE := true diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0ca8a2f..6ed9716 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,23 @@ + + @@ -38,7 +55,7 @@ - - @@ -74,8 +92,8 @@ - @@ -84,7 +102,7 @@ - + @@ -99,14 +117,14 @@ - - + diff --git a/res/color/popup_menu_text_color.xml b/res/color/popup_menu_text_color.xml index eb50db2..2931172 100644 --- a/res/color/popup_menu_text_color.xml +++ b/res/color/popup_menu_text_color.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/apply_cancel_bg.xml b/res/drawable/apply_cancel_bg.xml index 69cac8e..cadec5a 100644 --- a/res/drawable/apply_cancel_bg.xml +++ b/res/drawable/apply_cancel_bg.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/apply_circle_bg.xml b/res/drawable/apply_circle_bg.xml index 0ecea85..76b1704 100644 --- a/res/drawable/apply_circle_bg.xml +++ b/res/drawable/apply_circle_bg.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/apply_progress_background.xml b/res/drawable/apply_progress_background.xml index b1ea77a..d9a4fe1 100644 --- a/res/drawable/apply_progress_background.xml +++ b/res/drawable/apply_progress_background.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/apply_progress_bar.xml b/res/drawable/apply_progress_bar.xml index d6f1cbc..bd12589 100644 --- a/res/drawable/apply_progress_bar.xml +++ b/res/drawable/apply_progress_bar.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/bg_grid.xml b/res/drawable/bg_grid.xml index 92664aa..0e8764e 100644 --- a/res/drawable/bg_grid.xml +++ b/res/drawable/bg_grid.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + + + diff --git a/res/drawable/btn_quicktheme_remove_normal.xml b/res/drawable/btn_quicktheme_remove_normal.xml index f2f0bbe..88f33a5 100644 --- a/res/drawable/btn_quicktheme_remove_normal.xml +++ b/res/drawable/btn_quicktheme_remove_normal.xml @@ -1,4 +1,21 @@ + + diff --git a/res/drawable/component_selection_selector.xml b/res/drawable/component_selection_selector.xml index 1b61026..a95861d 100644 --- a/res/drawable/component_selection_selector.xml +++ b/res/drawable/component_selection_selector.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/component_selector_divider.xml b/res/drawable/component_selector_divider.xml index 8e0597f..004b4d0 100644 --- a/res/drawable/component_selector_divider.xml +++ b/res/drawable/component_selector_divider.xml @@ -1,4 +1,20 @@ + diff --git a/res/drawable/ic_action_reset.xml b/res/drawable/ic_action_reset.xml index 7c4303d..6b95fd3 100644 --- a/res/drawable/ic_action_reset.xml +++ b/res/drawable/ic_action_reset.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - 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. ---> - 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. ---> - 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. ---> + + diff --git a/res/drawable/quick_theme_fab.xml b/res/drawable/quick_theme_fab.xml index 762e014..53f5813 100644 --- a/res/drawable/quick_theme_fab.xml +++ b/res/drawable/quick_theme_fab.xml @@ -1,19 +1,21 @@ - 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. ---> + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/shop_themes_bg.xml b/res/drawable/shop_themes_bg.xml index 95afa02..f0384e9 100644 --- a/res/drawable/shop_themes_bg.xml +++ b/res/drawable/shop_themes_bg.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/wallpaper_border.xml b/res/drawable/wallpaper_border.xml index cc5a09d..a8062dc 100644 --- a/res/drawable/wallpaper_border.xml +++ b/res/drawable/wallpaper_border.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/drawable/wallpaper_none_bg.xml b/res/drawable/wallpaper_none_bg.xml index 782624c..3c4481f 100644 --- a/res/drawable/wallpaper_none_bg.xml +++ b/res/drawable/wallpaper_none_bg.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml index 6c7bab8..01a91a6 100644 --- a/res/layout/activity_main.xml +++ b/res/layout/activity_main.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + @@ -15,7 +29,7 @@ android:layout_height="match_parent" android:orientation="vertical" android:layout_gravity="center_vertical"> - - + diff --git a/res/layout/audible_card.xml b/res/layout/audible_card.xml index b291688..764a04e 100644 --- a/res/layout/audible_card.xml +++ b/res/layout/audible_card.xml @@ -1,14 +1,28 @@ - + + - - - + diff --git a/res/layout/bootani_component_selection_item.xml b/res/layout/bootani_component_selection_item.xml index 965a266..e595764 100644 --- a/res/layout/bootani_component_selection_item.xml +++ b/res/layout/bootani_component_selection_item.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - + + - - - + diff --git a/res/layout/bottom_actions.xml b/res/layout/bottom_actions.xml index eea6d7a..67f8461 100644 --- a/res/layout/bottom_actions.xml +++ b/res/layout/bottom_actions.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - - diff --git a/res/layout/component_selection_pager_item.xml b/res/layout/component_selection_pager_item.xml index 7764577..69eb205 100644 --- a/res/layout/component_selection_pager_item.xml +++ b/res/layout/component_selection_pager_item.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - + + - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/confirm_cancel_overlay.xml b/res/layout/confirm_cancel_overlay.xml index ae63a3b..1e13f18 100644 --- a/res/layout/confirm_cancel_overlay.xml +++ b/res/layout/confirm_cancel_overlay.xml @@ -1,8 +1,22 @@ - + + - - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/customize_reset_theme_layout.xml b/res/layout/customize_reset_theme_layout.xml index cd8a6cc..0c6872a 100644 --- a/res/layout/customize_reset_theme_layout.xml +++ b/res/layout/customize_reset_theme_layout.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - - + + - - - + diff --git a/res/layout/font_component_selection_item.xml b/res/layout/font_component_selection_item.xml index c520bc7..51e4799 100644 --- a/res/layout/font_component_selection_item.xml +++ b/res/layout/font_component_selection_item.xml @@ -1,14 +1,28 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - - - - - - + diff --git a/res/layout/icon_card.xml b/res/layout/icon_card.xml index 6b437c3..1cf454b 100644 --- a/res/layout/icon_card.xml +++ b/res/layout/icon_card.xml @@ -1,8 +1,22 @@ - + + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/icon_component_selection_item.xml b/res/layout/icon_component_selection_item.xml index 87db102..f2d4fe0 100644 --- a/res/layout/icon_component_selection_item.xml +++ b/res/layout/icon_component_selection_item.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - + + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/navigation_bar_component_selection_item.xml b/res/layout/navigation_bar_component_selection_item.xml index fef8ee8..8893ccb 100644 --- a/res/layout/navigation_bar_component_selection_item.xml +++ b/res/layout/navigation_bar_component_selection_item.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - + + + + diff --git a/res/layout/per_app_theme_list.xml b/res/layout/per_app_theme_list.xml index 54520c5..be595c9 100644 --- a/res/layout/per_app_theme_list.xml +++ b/res/layout/per_app_theme_list.xml @@ -1,11 +1,28 @@ - + + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/per_app_theme_list_item.xml b/res/layout/per_app_theme_list_item.xml index c426d5e..3601bef 100644 --- a/res/layout/per_app_theme_list_item.xml +++ b/res/layout/per_app_theme_list_item.xml @@ -1,4 +1,21 @@ + + + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - diff --git a/res/layout/sound_component_selection_item.xml b/res/layout/sound_component_selection_item.xml index e13c47c..08913d8 100644 --- a/res/layout/sound_component_selection_item.xml +++ b/res/layout/sound_component_selection_item.xml @@ -1,13 +1,27 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - + + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/status_bar_component_selection_item.xml b/res/layout/status_bar_component_selection_item.xml index cd84662..c9a7ee6 100644 --- a/res/layout/status_bar_component_selection_item.xml +++ b/res/layout/status_bar_component_selection_item.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - + + - - + diff --git a/res/layout/tag_applied.xml b/res/layout/tag_applied.xml index ab1d223..30fba6c 100644 --- a/res/layout/tag_applied.xml +++ b/res/layout/tag_applied.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - + + - + + - + + - + + - + + - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/wallpaper_card.xml b/res/layout/wallpaper_card.xml index ff2ab21..db56196 100644 --- a/res/layout/wallpaper_card.xml +++ b/res/layout/wallpaper_card.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + @@ -11,7 +25,7 @@ android:layout_height="match_parent" android:scaleType="centerCrop" /> - - + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + - - + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/values/colors.xml b/res/values/colors.xml index b043f91..bdc361a 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + #ffffff #ffffff diff --git a/res/values/dimens.xml b/res/values/dimens.xml index b5f9465..6f212e8 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/res/values/ids.xml b/res/values/ids.xml index 0e6acd1..c08edeb 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -1,4 +1,21 @@ + + diff --git a/res/values/integers.xml b/res/values/integers.xml index b2d17f3..e1c1e4c 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + 4 3 diff --git a/res/values/strings.xml b/res/values/strings.xml index 2fa25a8..6cc115a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + Theme Chooser @@ -94,4 +108,8 @@ Enable Live lock screen Animated lock screens require the Live lock screen feature. You can change this later by opening Settings and visiting the Live lock screen page under Lock screen options. Enable + + App not available + https://play.google.com/store/search?q=theme+engine&c=apps + http://wiki.cyanogenmod.org/w/Themes diff --git a/res/values/styles.xml b/res/values/styles.xml index e0bc961..96fced3 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -1,7 +1,21 @@ + Copyright (C) 2016 Cyanogen, Inc. + Copyright (C) 2016 The CyanogenMod 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. + --> + diff --git a/src/android/support/v4/view/ThemeViewPager.java b/src/android/support/v4/view/ThemeViewPager.java index cae4d5a..ba0911e 100644 --- a/src/android/support/v4/view/ThemeViewPager.java +++ b/src/android/support/v4/view/ThemeViewPager.java @@ -1,6 +1,20 @@ /* - * Copyright (C) 2014 Cyanogen, Inc. + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + package android.support.v4.view; import android.content.Context; @@ -10,7 +24,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import com.cyngn.theme.chooser.R; +import org.cyanogenmod.theme.chooser.R; public class ThemeViewPager extends ViewPager { private static final String TAG = ThemeViewPager.class.getSimpleName(); diff --git a/src/android/widget/ComponentSelectorLinearLayout.java b/src/android/widget/ComponentSelectorLinearLayout.java index cb13ba0..17d4256 100644 --- a/src/android/widget/ComponentSelectorLinearLayout.java +++ b/src/android/widget/ComponentSelectorLinearLayout.java @@ -1,6 +1,20 @@ /* - * Copyright (C) 2014 The Cyanogen, Inc + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + package android.widget; import android.content.Context; diff --git a/src/com/cyngn/theme/chooser/AppReceiver.java b/src/com/cyngn/theme/chooser/AppReceiver.java deleted file mode 100644 index 4354f63..0000000 --- a/src/com/cyngn/theme/chooser/AppReceiver.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.chooser; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.text.TextUtils; - -import com.cyngn.theme.util.NotificationHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract; - -public class AppReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Uri uri = intent.getData(); - String pkgName = uri != null ? uri.getSchemeSpecificPart() : null; - String action = intent.getAction(); - - if (cyanogenmod.content.Intent.ACTION_THEME_INSTALLED.equals(action)) { - if (!pkgName.equals(Utils.getDefaultThemePackageName(context))) { - NotificationHelper.postThemeInstalledNotification(context, pkgName); - } - } else if (cyanogenmod.content.Intent.ACTION_THEME_REMOVED.equals(action)) { - // remove updated status for this theme (if one exists) - PreferenceUtils.removeUpdatedTheme(context, pkgName); - - // If the theme being removed was the currently applied theme we need - // to update the applied base theme in preferences to the default theme. - String appliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(context); - if (!TextUtils.isEmpty(appliedBaseTheme) && appliedBaseTheme.equals(pkgName)) { - PreferenceUtils.setAppliedBaseTheme(context, - Utils.getDefaultThemePackageName(context)); - } - NotificationHelper.cancelNotifications(context); - } else if (cyanogenmod.content.Intent.ACTION_THEME_UPDATED.equals(action)) { - try { - if (isTheme(context, pkgName)) { - PreferenceUtils.addUpdatedTheme(context, pkgName); - } - } catch (NameNotFoundException e) { - } - } - } - - private boolean isTheme(Context context, String pkgName) throws NameNotFoundException { - PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); - - return pi != null && pi.themeInfo != null; - } -} diff --git a/src/com/cyngn/theme/chooser/BootReceiver.java b/src/com/cyngn/theme/chooser/BootReceiver.java deleted file mode 100644 index 8240bfc..0000000 --- a/src/com/cyngn/theme/chooser/BootReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -public class BootReceiver extends BroadcastReceiver { - private static final String CHOOSER_PKG_NAME = "com.cyngn.theme.chooser"; - private static final String CHOOSER_ACTIVITY = "com.cyngn.theme.chooser.ChooserLauncher"; - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - PackageManager pm = context.getPackageManager(); - if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - try { - PackageInfo info = pm.getPackageInfo(ChooserActivity.THEME_STORE_PACKAGE, 0); - if (info != null) { - ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, CHOOSER_ACTIVITY); - pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - } catch (PackageManager.NameNotFoundException e) { - // no store so nothing to do. - } - - // now disable this receiver so we don't get called on future boots - ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, - BootReceiver.class.getCanonicalName()); - pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - } -} diff --git a/src/com/cyngn/theme/chooser/ChooserActivity.java b/src/com/cyngn/theme/chooser/ChooserActivity.java deleted file mode 100644 index d607eb6..0000000 --- a/src/com/cyngn/theme/chooser/ChooserActivity.java +++ /dev/null @@ -1,1191 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.IPackageDeleteObserver; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.RemoteException; -import android.provider.Settings; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RenderScript; -import android.renderscript.ScriptIntrinsicBlur; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.support.v4.view.ThemeViewPager; -import android.support.v4.view.ViewPager; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MutableLong; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; -import android.widget.ImageView; - -import com.cyngn.theme.perapptheming.PerAppThemingWindow; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.NotificationHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.platform.Manifest; -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeManager; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; - -import static com.cyngn.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_INSTALLED_THEMES; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_APPLIED; - -public class ChooserActivity extends FragmentActivity - implements LoaderManager.LoaderCallbacks { - public static final String THEME_STORE_PACKAGE = "com.cyngn.themestore"; - private static final String TAG = ChooserActivity.class.getSimpleName(); - - public static final String DEFAULT = ThemeConfig.SYSTEM_DEFAULT; - public static final String EXTRA_PKGNAME = "pkgName"; - public static final String EXTRA_COMPONENTS = "components"; - - private static final int OFFSCREEN_PAGE_LIMIT = 3; - - private static final String THEME_STORE_ACTIVITY = THEME_STORE_PACKAGE + ".ui.StoreActivity"; - private static final String ACTION_APPLY_THEME = "android.intent.action.APPLY_THEME"; - - private static final String TYPE_IMAGE = "image/*"; - - private static final String CYNGN_THEMES_PERMISSION = - "com.cyngn.themes.permission.THEMES_APP"; - private static final String ACTION_CHOOSER_OPENED = - "com.cyngn.themes.action.CHOOSER_OPENED"; - private static final String ACTION_THEME_REMOVED = - "com.cyngn.themes.action.THEME_REMOVED_FROM_CHOOSER"; - private static final String EXTRA_PACKAGE = "package"; - - private static final String ACTION_PICK_LOCK_SCREEN_WALLPAPER = - "com.cyngn.intent.action.PICK_LOCK_SCREEN_WALLPAPER"; - - /** - * Request code for picking an external wallpaper - */ - public static final int REQUEST_PICK_WALLPAPER_IMAGE = 2; - /** - * Request code for picking an external lockscreen wallpaper - */ - public static final int REQUEST_PICK_LOCKSCREEN_IMAGE = 3; - - /** - * Request code for enabling system alert window permission - */ - private static final int REQUEST_SYSTEM_WINDOW_PERMISSION = 4; - - private static final long ANIMATE_CONTENT_IN_SCALE_DURATION = 500; - private static final long ANIMATE_CONTENT_IN_ALPHA_DURATION = 750; - private static final long ANIMATE_CONTENT_IN_BLUR_DURATION = 250; - private static final long ANIMATE_CONTENT_DELAY = 250; - private static final long ANIMATE_SHOP_THEMES_HIDE_DURATION = 250; - private static final long ANIMATE_SHOP_THEMES_SHOW_DURATION = 500; - private static final long FINISH_ANIMATION_DELAY = ThemeFragment.ANIMATE_DURATION - + ThemeFragment.ANIMATE_START_DELAY + 250; - - private static final long ANIMATE_CARDS_IN_DURATION = 250; - private static final long ANIMATE_SAVE_APPLY_LAYOUT_DURATION = 300; - private static final float ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR = 3; - private static final long ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY = 400; - - private PagerContainer mContainer; - private ThemeViewPager mPager; - - private ThemesAdapter mAdapter; - private boolean mExpanded = false; - private ComponentSelector mSelector; - private View mSaveApplyLayout; - private int mContainerYOffset = 0; - private TypefaceHelperCache mTypefaceHelperCache; - private boolean mIsAnimating; - private Handler mHandler; - private View mBottomActionsLayout; - - private String mSelectedTheme; - private String mAppliedBaseTheme; - private boolean mThemeChanging = false; - private boolean mAnimateContentIn = false; - private long mAnimateContentInDelay; - private String mThemeToApply; - private ArrayList mComponentsToApply; - - ImageView mCustomBackground; - - // Current system theme configuration as component -> pkgName - private Map mCurrentTheme = new HashMap(); - private MutableLong mCurrentWallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); - - private boolean mIsPickingImage = false; - private boolean mRestartLoaderOnCollapse = false; - private boolean mActivityResuming = false; - private boolean mShowLockScreenWallpaper = false; - - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - NotificationHijackingService.ensureEnabled(this); - - if (savedInstanceState == null) { - handleIntent(getIntent()); - } - - mContainer = (PagerContainer) findViewById(R.id.pager_container); - mPager = (ThemeViewPager) findViewById(R.id.viewpager); - - mPager.setOnClickListener(mPagerClickListener); - mAdapter = new ThemesAdapter(); - mPager.setAdapter(mAdapter); - - DisplayMetrics dm = getResources().getDisplayMetrics(); - int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, dm); - mPager.setPageMargin(-margin / 2); - mPager.setOffscreenPageLimit(OFFSCREEN_PAGE_LIMIT); - - mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - public void onPageSelected(int position) { - } - - public void onPageScrolled(int position, - float positionOffset, - int positionOffsetPixels) { - } - - public void onPageScrollStateChanged(int state) { - } - }); - - mSelector = (ComponentSelector) findViewById(R.id.component_selector); - mSelector.setOnOpenCloseListener(mOpenCloseListener); - - mBottomActionsLayout = findViewById(R.id.bottom_actions_layout); - - mSaveApplyLayout = findViewById(R.id.save_apply_layout); - mSaveApplyLayout.findViewById(R.id.save_apply_button).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mIsAnimating) return; - hideSaveApplyButton(); - mContainer.setClickable(false); - final ThemeFragment f = getCurrentFragment(); - if (mSelector.isEnabled()) { - mSelector.hide(); - if (mContainerYOffset != 0) { - slideContentBack(-mContainerYOffset); - mContainerYOffset = 0; - } - if (f != null) f.fadeInCards(); - if (mShowLockScreenWallpaper) { - mShowLockScreenWallpaper = false; - mSelector.resetComponentType(); - } - } - - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - collapse(true); - } - }, ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY); - } - }); - - mBottomActionsLayout.findViewById(R.id.shop_themes) - .setOnClickListener(mOnShopThemesClicked); - - mTypefaceHelperCache = TypefaceHelperCache.getInstance(); - mHandler = new Handler(); - mCustomBackground = (ImageView) findViewById(R.id.custom_bg); - mAnimateContentIn = true; - mAnimateContentInDelay = 0; - - mBottomActionsLayout.findViewById(R.id.per_app_theming).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (Settings.canDrawOverlays(ChooserActivity.this)) { - launchAppThemer(); - } else { - requestSystemWindowPermission(); - } - } - }); - - if (shouldHideShopThemes()) { - mBottomActionsLayout.findViewById(R.id.shop_themes).setVisibility(View.GONE); - } - if (PreferenceUtils.getShowPerAppThemeNewTag(this)) { - View tag = mBottomActionsLayout.findViewById(R.id.new_tag); - if (tag != null) { - tag.setVisibility(View.VISIBLE); - } - } - } - - public void showSaveApplyButton() { - if (mSaveApplyLayout != null && mSaveApplyLayout.getVisibility() != View.VISIBLE) { - mHandler.post(new Runnable() { - @Override - public void run() { - int navBarHeight = 0; - if (Utils.hasNavigationBar(ChooserActivity.this.getApplicationContext())) { - navBarHeight = ChooserActivity.this.getResources() - .getDimensionPixelSize(R.dimen.navigation_bar_height); - } - mSaveApplyLayout.setTranslationY(mSaveApplyLayout.getMeasuredHeight()); - mSaveApplyLayout.setVisibility(View.VISIBLE); - mSaveApplyLayout.animate() - .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION) - .setInterpolator( - new DecelerateInterpolator( - ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) - .translationY(-mSelector.getMeasuredHeight() - + navBarHeight); - } - }); - } - } - - public void hideSaveApplyButton() { - if (mSaveApplyLayout.getVisibility() != View.GONE) { - Animation anim = AnimationUtils.loadAnimation(this, - R.anim.component_selection_animate_out); - mSaveApplyLayout.startAnimation(anim); - anim.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mSaveApplyLayout.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - } - } - - private void hideBottomActionsLayout() { - final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); - anim.alpha(0f).setDuration(ANIMATE_SHOP_THEMES_HIDE_DURATION); - anim.setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mBottomActionsLayout.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } - - private void showBottomActionsLayout() { - mBottomActionsLayout.setVisibility(View.VISIBLE); - final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); - anim.setListener(null); - anim.alpha(1f).setStartDelay(ThemeFragment.ANIMATE_DURATION) - .setDuration(ANIMATE_SHOP_THEMES_SHOW_DURATION); - } - - private boolean shouldHideShopThemes() { - boolean hasThemeStore = false; - try { - if (getPackageManager().getPackageInfo(THEME_STORE_PACKAGE, 0) != null) { - hasThemeStore = true; - } - } catch (PackageManager.NameNotFoundException e) { - - } - return !hasThemeStore || Utils.isRecentTaskThemeStore(this); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - if (Utils.isRecentTaskHome(this)) { - mContainer.setAlpha(0f); - mBottomActionsLayout.setAlpha(0f); - mAnimateContentIn = true; - mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; - } - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - String action = intent.getAction(); - if ((Intent.ACTION_MAIN.equals(action) || ACTION_APPLY_THEME.equals(action)) - && intent.hasExtra(EXTRA_PKGNAME)) { - if (intent.hasExtra(EXTRA_COMPONENTS)) { - mComponentsToApply = intent.getStringArrayListExtra(EXTRA_COMPONENTS); - } else { - mComponentsToApply = null; - } - mSelectedTheme = mComponentsToApply != null ? - PreferenceUtils.getAppliedBaseTheme(this) : - getSelectedTheme(intent.getStringExtra(EXTRA_PKGNAME)); - if (mPager != null) { - startLoader(LOADER_ID_INSTALLED_THEMES); - if (mExpanded) { - int collapseDelay = ThemeFragment.ANIMATE_START_DELAY; - if (mSelector.isEnabled()) { - // onBackPressed() has all the necessary logic for collapsing the - // component selector, so we call it here. - onBackPressed(); - collapseDelay += ThemeFragment.ANIMATE_DURATION; - } - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - collapse(false); - } - }, collapseDelay); - } - } - - if (ACTION_APPLY_THEME.equals(action) && - getCallingPackage() != null && - PackageManager.PERMISSION_GRANTED == - getPackageManager() - .checkPermission(Manifest.permission.WRITE_THEMES, - getCallingPackage())) { - mThemeToApply = intent.getStringExtra(EXTRA_PKGNAME); - } - } else if (ACTION_PICK_LOCK_SCREEN_WALLPAPER.equals(action)) { - mShowLockScreenWallpaper = true; - } - } - - private String getSelectedTheme(String requestedTheme) { - String[] projection = { ThemesColumns.PRESENT_AS_THEME }; - String selection = ThemesColumns.PKG_NAME + "=?"; - String[] selectionArgs = { requestedTheme }; - - String selectedTheme = PreferenceUtils.getAppliedBaseTheme(this); - - Cursor cursor = getContentResolver().query(ThemesColumns.CONTENT_URI, - projection, selection, selectionArgs, null); - if (cursor != null) { - if (cursor.getCount() > 0 && cursor.moveToFirst()) { - if (cursor.getInt(0) == 1) { - selectedTheme = requestedTheme; - } - } - cursor.close(); - } - return selectedTheme; - } - - private void setAnimatingStateAndScheduleFinish() { - mIsAnimating = true; - mContainer.setIsAnimating(true); - mHandler.postDelayed(new Runnable() { - public void run() { - mIsAnimating = false; - mContainer.setIsAnimating(false); - if (mRestartLoaderOnCollapse) { - mRestartLoaderOnCollapse = false; - startLoader(LOADER_ID_INSTALLED_THEMES); - } - } - }, FINISH_ANIMATION_DELAY); - if (mExpanded) { - hideBottomActionsLayout(); - } else { - showBottomActionsLayout(); - } - } - - private void setCustomBackground(final ImageView iv, final boolean animate) { - final Context context = ChooserActivity.this; - iv.post(new Runnable() { - @Override - public void run() { - Bitmap tmpBmp; - try { - tmpBmp = Utils.getRegularWallpaperBitmap(context); - } catch (Throwable e) { - Log.w(TAG, "Failed to retrieve wallpaper", e); - tmpBmp = null; - } - // Show the grid background if no wallpaper is set. - // Note: no wallpaper is actually a 1x1 pixel black bitmap - if (tmpBmp == null || tmpBmp.getWidth() <= 1 || tmpBmp.getHeight() <= 1) { - iv.setImageResource(R.drawable.bg_grid); - // We need to change the ScaleType to FIT_XY otherwise the background is cut - // off a bit at the bottom. - iv.setScaleType(ImageView.ScaleType.FIT_XY); - return; - } - - // Since we are applying a blur, we can afford to scale the bitmap down and use a - // smaller blur radius. - Bitmap inBmp = Bitmap.createScaledBitmap(tmpBmp, tmpBmp.getWidth() / 4, - tmpBmp.getHeight() / 4, false); - Bitmap outBmp = Bitmap.createBitmap(inBmp.getWidth(), inBmp.getHeight(), - Bitmap.Config.ARGB_8888); - - // Blur the original bitmap - RenderScript rs = RenderScript.create(context); - ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); - Allocation tmpIn = Allocation.createFromBitmap(rs, inBmp); - Allocation tmpOut = Allocation.createFromBitmap(rs, outBmp); - theIntrinsic.setRadius(5.0f); - theIntrinsic.setInput(tmpIn); - theIntrinsic.forEach(tmpOut); - tmpOut.copyTo(outBmp); - - // Create a bitmap drawable and use a color matrix to de-saturate the image - BitmapDrawable[] layers = new BitmapDrawable[2]; - layers[0] = new BitmapDrawable(getResources(), tmpBmp); - layers[1] = new BitmapDrawable(getResources(), outBmp); - ColorMatrix cm = new ColorMatrix(); - cm.setSaturation(0); - Paint p = layers[0].getPaint(); - p.setColorFilter(new ColorMatrixColorFilter(cm)); - p = layers[1].getPaint(); - p.setColorFilter(new ColorMatrixColorFilter(cm)); - TransitionDrawable d = new TransitionDrawable(layers); - - // All done - iv.setScaleType(ImageView.ScaleType.CENTER_CROP); - if (!animate) { - iv.setImageDrawable(layers[1]); - } else { - iv.setImageDrawable(d); - } - } - }); - } - - /** - * Disable the ViewPager while a theme change is occuring - */ - public void themeChangeStart() { - lockPager(); - mThemeChanging = true; - ThemeFragment f = getCurrentFragment(); - if (f != null) { - mAppliedBaseTheme = f.getThemePackageName(); - PreferenceUtils.setAppliedBaseTheme(this, mAppliedBaseTheme); - } - } - - /** - * Re-enable the ViewPager and update the "My theme" fragment if available - */ - public void themeChangeEnd(boolean isSuccess) { - mThemeChanging = false; - ThemeFragment f = getCurrentFragment(); - if (f != null) { - // We currently need to recreate the adapter in order to load - // the changes otherwise the adapter returns the original fragments - // TODO: We'll need a better way to handle this to provide a good UX - if (!(f instanceof MyThemeFragment)) { - mAdapter = new ThemesAdapter(); - mPager.setAdapter(mAdapter); - } - if (!isSuccess) { - mAppliedBaseTheme = null; - } - startLoader(LOADER_ID_APPLIED); - } - unlockPager(); - } - - public void lockPager() { - mPager.setScrollingEnabled(false); - } - - public void unlockPager() { - mPager.setScrollingEnabled(true); - } - - public ComponentSelector getComponentSelector() { - return mSelector; - } - - public void showComponentSelector(String component, View v) { - showComponentSelector(component, null, DEFAULT_COMPONENT_ID, v); - } - - public void showComponentSelector(String component, String selectedPkgName, - long selectedCmpntId, View v) { - if (component != null) { - final Resources res = getResources(); - int itemsPerPage = res.getInteger(R.integer.default_items_per_page); - int height = res.getDimensionPixelSize(R.dimen.component_selection_cell_height); - if (MODIFIES_BOOT_ANIM.equals(component)) { - itemsPerPage = res.getInteger(R.integer.bootani_items_per_page); - height = res.getDimensionPixelSize( - R.dimen.component_selection_cell_height_boot_anim); - } else if (MODIFIES_ALARMS.equals(component) || - MODIFIES_NOTIFICATIONS.equals(component) || - MODIFIES_RINGTONES.equals(component)) { - itemsPerPage = 2; - height = res.getDimensionPixelSize( - R.dimen.component_selection_cell_height_sounds); - } - if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { - if (mSaveApplyLayout.getTranslationY() + height != 0) { - mSaveApplyLayout.animate() - .translationY(-height) - .setInterpolator( - new DecelerateInterpolator( - ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) - .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); - } - } - mSelector.show(component, selectedPkgName, selectedCmpntId, itemsPerPage, height); - - // determine if we need to shift the cards up - int[] coordinates = new int[2]; - v.getLocationOnScreen(coordinates); - final int top = coordinates[1]; - final int bottom = coordinates[1] + v.getHeight(); - final int statusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); - int selectorTop = getWindowManager().getDefaultDisplay().getHeight() - height; - if (bottom > selectorTop) { - slideContentIntoView(bottom - selectorTop, height); - } else if (top < statusBarHeight) { - slideContentIntoView(top - statusBarHeight, height); - } - } - } - - public void expand() { - if (!mExpanded && !mIsAnimating) { - mExpanded = true; - mContainer.setClickable(false); - mContainer.expand(); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.expand(); - } - setAnimatingStateAndScheduleFinish(); - } - } - - public void collapse(final boolean applyTheme) { - mExpanded = false; - final ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.fadeOutCards(new Runnable() { - public void run() { - mContainer.collapse(); - f.collapse(applyTheme); - } - }); - } - setAnimatingStateAndScheduleFinish(); - } - - public void pickExternalWallpaper() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType(TYPE_IMAGE); - startActivityForResult(intent, REQUEST_PICK_WALLPAPER_IMAGE); - mIsPickingImage = true; - } - - public void pickExternalLockscreen() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType(TYPE_IMAGE); - startActivityForResult(intent, REQUEST_PICK_LOCKSCREEN_IMAGE); - mIsPickingImage = true; - } - - public void uninstallTheme(String pkgName) { - PackageManager pm = getPackageManager(); - pm.deletePackage(pkgName, new PackageDeleteObserver(), PackageManager.DELETE_ALL_USERS); - sendThemeRemovedBroadcast(pkgName); - } - - private void slideContentIntoView(int yDelta, int selectorHeight) { - ThemeFragment f = getCurrentFragment(); - if (f != null) { - final int offset = getResources().getDimensionPixelSize(R.dimen.content_offset_padding); - if (yDelta > 0) { - yDelta += offset; - } else { - yDelta -= offset; - } - f.slideContentIntoView(yDelta, selectorHeight); - mContainerYOffset = yDelta; - } - } - - private void slideContentBack(final int yDelta) { - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.slideContentBack(yDelta); - } - } - - @Override - protected void onResume() { - super.onResume(); - setCustomBackground(mCustomBackground, mAnimateContentIn); - // clear out any notifications that are being displayed. - NotificationHelper.cancelNotifications(this); - - ThemeManager tm = ThemeManager.getInstance(this); - mThemeChanging = tm.isThemeApplying(); - - if (!mIsPickingImage) { - startLoader(LOADER_ID_APPLIED); - } else { - mIsPickingImage = false; - } - - IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - registerReceiver(mWallpaperChangeReceiver, filter); - } - - @Override - public void onBackPressed() { - final ThemeFragment f = getCurrentFragment(); - if (mSelector.isEnabled()) { - mSelector.hide(); - if (mContainerYOffset != 0) { - slideContentBack(-mContainerYOffset); - mContainerYOffset = 0; - } - if (f != null) f.fadeInCards(); - if (mShowLockScreenWallpaper) { - mShowLockScreenWallpaper = false; - mSelector.resetComponentType(); - } - } else if (mExpanded) { - if (mIsAnimating) { - return; - } - - if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { - hideSaveApplyButton(); - if (f != null) f.clearChanges(); - } - collapse(false); - } else { - if (f != null && f.isShowingConfirmCancelOverlay()) { - f.hideConfirmCancelOverlay(); - } else if (f != null && f.isShowingCustomizeResetLayout()) { - f.hideCustomizeResetLayout(); - } else { - super.onBackPressed(); - } - } - } - - @Override - public void onPause() { - super.onPause(); - unregisterReceiver(mWallpaperChangeReceiver); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - mSelectedTheme = f.getThemePackageName(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - @Override - protected void onStart() { - super.onStart(); - if (mTypefaceHelperCache.getTypefaceCount() <= 0) { - new TypefacePreloadTask().execute(); - } - sendChooserOpenedBroadcast(); - mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_WALLPAPER_IMAGE) { - if (data != null && data.getData() != null) { - Uri uri = data.getData(); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.setWallpaperImageUri(uri); - showSaveApplyButton(); - } - } - } else if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_LOCKSCREEN_IMAGE) { - if (data != null && data.getData() != null) { - Uri uri = data.getData(); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.setLockscreenImageUri(uri); - showSaveApplyButton(); - } - } - } else if (requestCode == REQUEST_SYSTEM_WINDOW_PERMISSION) { - if (Settings.canDrawOverlays(this)) { - launchAppThemer(); - } - } - } - - private void sendChooserOpenedBroadcast() { - sendBroadcast(new Intent(ACTION_CHOOSER_OPENED), CYNGN_THEMES_PERMISSION); - } - - private void sendThemeRemovedBroadcast(String pkgName) { - Intent intent = new Intent(ACTION_THEME_REMOVED); - intent.putExtra(EXTRA_PACKAGE, pkgName); - sendBroadcast(intent, CYNGN_THEMES_PERMISSION); - } - - private void animateContentIn() { - Drawable d = mCustomBackground.getDrawable(); - if (d instanceof TransitionDrawable) { - ((TransitionDrawable) d).startTransition((int) ANIMATE_CONTENT_IN_BLUR_DURATION); - } - - if (!mShowLockScreenWallpaper) { - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) - .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION)) - .with(ObjectAnimator.ofFloat(mContainer, "scaleX", 2f, 1f) - .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)) - .with(ObjectAnimator.ofFloat(mContainer, "scaleY", 2f, 1f) - .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)); - set.setStartDelay(mAnimateContentInDelay); - set.start(); - mBottomActionsLayout.setAlpha(0f); - mBottomActionsLayout.animate().alpha(1f).setStartDelay(mAnimateContentInDelay) - .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION); - } else { - mContainer.setAlpha(0f); - mContainer.setVisibility(View.GONE); - } - mAnimateContentIn = false; - } - - private View.OnClickListener mPagerClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - ThemeFragment f = getCurrentFragment(); - if (f != null && !mThemeChanging) { - f.performClick(mPager.isClickedOnContent()); - } - } - }; - - private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mCustomBackground != null) setCustomBackground(mCustomBackground, false); - } - }; - - private ComponentSelector.OnOpenCloseListener mOpenCloseListener = new ComponentSelector.OnOpenCloseListener() { - @Override - public void onSelectorOpened() { - } - - @Override - public void onSelectorClosed() { - } - - @Override - public void onSelectorClosing() { - ThemeFragment f = getCurrentFragment(); - if (f != null && f.componentsChanged() - && mSaveApplyLayout.getVisibility() == View.VISIBLE) { - mSaveApplyLayout.animate() - .translationY(0) - .setInterpolator(new DecelerateInterpolator()) - .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); - } - } - }; - - private ThemeFragment getCurrentFragment() { - // instantiateItem will return the fragment if it already exists and not instantiate it, - // which should be the case for the current fragment. - ThemeFragment f; - try { - f = (mAdapter == null || mPager == null || mAdapter.getCount() <= 0) ? null : - (ThemeFragment) mAdapter.instantiateItem(mPager, mPager.getCurrentItem()); - } catch (Exception e) { - f = null; - Log.e(TAG, "Unable to get current fragment", e); - } - return f; - } - - private void populateCurrentTheme(Cursor c) { - c.moveToPosition(-1); - //Default to first wallpaper - mCurrentWallpaperCmpntId.value = DEFAULT_COMPONENT_ID; - // clear out the previous map - mCurrentTheme.clear(); - while(c.moveToNext()) { - int mixkeyIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_KEY); - int pkgIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_VALUE); - int cmpntIdIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_COMPONENT_ID); - String mixkey = c.getString(mixkeyIdx); - String component = ThemesContract.MixnMatchColumns.mixNMatchKeyToComponent(mixkey); - String pkg = c.getString(pkgIdx); - mCurrentTheme.put(component, pkg); - if (TextUtils.equals(component, ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { - mCurrentTheme.remove(ThemesColumns.MODIFIES_LOCKSCREEN); - } - if (TextUtils.equals(component, ThemesColumns.MODIFIES_LOCKSCREEN)) { - mCurrentTheme.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); - } - if (cmpntIdIdx >= 0 && TextUtils.equals(component, ThemesColumns.MODIFIES_LAUNCHER)) { - mCurrentWallpaperCmpntId.value = c.getLong(cmpntIdIdx); - } - } - } - - private View.OnClickListener mOnShopThemesClicked = new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(); - intent.setClassName(THEME_STORE_PACKAGE, THEME_STORE_ACTIVITY); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Unable to launch Theme Store", e); - } - } - }; - - private void startLoader(int loaderId) { - final LoaderManager manager = getSupportLoaderManager(); - final Loader loader = manager.getLoader(loaderId); - if (loader != null) { - manager.restartLoader(loaderId, null, this); - } else { - manager.initLoader(loaderId, null, this); - } - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - if (mThemeChanging) return; - - if (mExpanded && !mActivityResuming) { - mRestartLoaderOnCollapse = true; - return; - } - - switch (loader.getId()) { - case LOADER_ID_INSTALLED_THEMES: - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - int selectedThemeIndex = -1; - if (TextUtils.isEmpty(mSelectedTheme)) mSelectedTheme = mAppliedBaseTheme; - while(data.moveToNext()) { - if (mSelectedTheme.equals(data.getString( - data.getColumnIndex(ThemesColumns.PKG_NAME)))) { - // we need to add one here since the first card is "My theme" - selectedThemeIndex = data.getPosition(); - mSelectedTheme = null; - break; - } - } - data.moveToFirst(); - mAdapter.swapCursor(data); - mAdapter.notifyDataSetChanged(); - if (selectedThemeIndex >= 0) { - mPager.setCurrentItem(selectedThemeIndex, false); - - if (mThemeToApply != null) { - ThemeFragment f = getCurrentFragment(); - f.applyThemeWhenPopulated(mThemeToApply, mComponentsToApply); - mThemeToApply = null; - } - } - if (mAnimateContentIn) animateContentIn(); - mActivityResuming = true; - break; - case LOADER_ID_APPLIED: - startLoader(LOADER_ID_INSTALLED_THEMES); - populateCurrentTheme(data); - break; - } - } - - @Override - public void onLoaderReset(Loader loader) { - switch (loader.getId()) { - case LOADER_ID_INSTALLED_THEMES: - mAdapter.swapCursor(null); - mAdapter.notifyDataSetChanged(); - break; - } - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_INSTALLED_THEMES: - mAppliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(this); - break; - case LOADER_ID_APPLIED: - //TODO: Mix n match query should only be done once - break; - } - return CursorLoaderHelper.chooserActivityCursorLoader(this, id, mAppliedBaseTheme); - } - - public Map getSelectedComponentsMap() { - return getCurrentFragment().getSelectedComponentsMap(); - } - - public class ThemesAdapter extends NewFragmentStatePagerAdapter { - private ArrayList mInstalledThemes; - private String mAppliedThemeTitle; - private String mAppliedThemeAuthor; - private HashMap mRepositionedFragments; - - public ThemesAdapter() { - super(getSupportFragmentManager()); - mRepositionedFragments = new HashMap(); - } - - @Override - public Fragment getItem(int position) { - ThemeFragment f = null; - MutableLong wallpaperCmpntId; - if (mInstalledThemes != null) { - final String pkgName = mInstalledThemes.get(position); - if (pkgName.equals(mAppliedBaseTheme)) { - f = MyThemeFragment.newInstance(mAppliedBaseTheme, mAppliedThemeTitle, - mAppliedThemeAuthor, mAnimateContentIn, mShowLockScreenWallpaper); - wallpaperCmpntId = mCurrentWallpaperCmpntId; - } else { - f = ThemeFragment.newInstance(pkgName, mAnimateContentIn); - wallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); - } - f.setCurrentTheme(mCurrentTheme, wallpaperCmpntId); - } - return f; - } - - @Override - public long getItemId(int position) { - if (mInstalledThemes != null) { - final String pkgName = mInstalledThemes.get(position); - return pkgName.hashCode(); - } - return 0; - } - - @Override - public int getItemPosition(Object object) { - final ThemeFragment f = (ThemeFragment) object; - final String pkgName = f != null ? f.getThemePackageName() : null; - if (pkgName != null && mRepositionedFragments.containsKey(pkgName)) { - final int position = mRepositionedFragments.get(pkgName); - mRepositionedFragments.remove(pkgName); - return position; - } - return super.getItemPosition(object); - } - - /** - * The first card should be the user's currently applied theme components so we - * will always return at least 1 or mCursor.getCount() + 1 - * @return - */ - public int getCount() { - return mInstalledThemes == null ? 0 : mInstalledThemes.size(); - } - - public void swapCursor(Cursor c) { - if (c != null && c.getCount() != 0) { - ArrayList previousOrder = mInstalledThemes == null ? null - : new ArrayList(mInstalledThemes); - mInstalledThemes = new ArrayList(c.getCount()); - mRepositionedFragments.clear(); - c.moveToPosition(-1); - while (c.moveToNext()) { - final int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - final String pkgName = c.getString(pkgIdx); - if (pkgName.equals(mAppliedBaseTheme)) { - final int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); - final int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); - mAppliedThemeTitle = c.getString(titleIdx); - mAppliedThemeAuthor = c.getString(authorIdx); - } - mInstalledThemes.add(pkgName); - - // track any themes that may have changed position - if (previousOrder != null && previousOrder.contains(pkgName)) { - int index = previousOrder.indexOf(pkgName); - if (index != c.getPosition()) { - mRepositionedFragments.put(pkgName, c.getPosition()); - } - } else { - mRepositionedFragments.put(pkgName, c.getPosition()); - } - } - // check if any themes are no longer in the new list - if (previousOrder != null) { - for (String pkgName : previousOrder) { - if (!mInstalledThemes.contains(pkgName)) { - mRepositionedFragments.put(pkgName, POSITION_NONE); - } - } - } - } else { - mInstalledThemes = null; - } - } - - public void removeTheme(String pkgName) { - if (mInstalledThemes == null) return; - - if (mInstalledThemes.contains(pkgName)) { - final int count = mInstalledThemes.size(); - final int index = mInstalledThemes.indexOf(pkgName); - // reposition all the fragments after the one being removed - for (int i = index + 1; i < count; i++) { - mRepositionedFragments.put(mInstalledThemes.get(i), i - 1); - } - // Now remove this theme and add it to mRepositionedFragments with POSITION_NONE - mInstalledThemes.remove(pkgName); - mRepositionedFragments.put(pkgName, POSITION_NONE); - // now we can call notifyDataSetChanged() - notifyDataSetChanged(); - } - } - } - - private class TypefacePreloadTask extends AsyncTask { - - @Override - protected Object doInBackground(Object[] params) { - String[] projection = { ThemesColumns.PKG_NAME }; - String selection = ThemesColumns.MODIFIES_FONTS + "=?"; - String[] selectionArgs = { "1" }; - Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, projection, selection, - selectionArgs, null); - if (c != null) { - while (c.moveToNext()) { - mTypefaceHelperCache.getHelperForTheme(ChooserActivity.this, c.getString(0)); - } - c.close(); - } - return null; - } - } - - /** - * Internal delete callback from the system - */ - class PackageDeleteObserver extends IPackageDeleteObserver.Stub { - public void packageDeleted(final String packageName, int returnCode) throws RemoteException { - if (returnCode == PackageManager.DELETE_SUCCEEDED) { - Log.d(TAG, "Delete succeeded"); - mHandler.post(new Runnable() { - @Override - public void run() { - mAdapter.removeTheme(packageName); - } - }); - } else { - Log.e(TAG, "Delete failed with returnCode " + returnCode); - } - } - } - - public void expandContentAndAnimateLockScreenCardIn() { - mHandler.post(new Runnable() { - @Override - public void run() { - expand(); - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) - .setDuration(ANIMATE_CARDS_IN_DURATION)); - set.setStartDelay(mAnimateContentInDelay); - set.start(); - mContainer.setVisibility(View.VISIBLE); - getCurrentFragment().showLockScreenCard(); - } - }, ANIMATE_CARDS_IN_DURATION); - } - }); - } - - private void launchAppThemer() { - PreferenceUtils.setShowPerAppThemeNewTag(ChooserActivity.this, false); - Intent intent = new Intent(ChooserActivity.this, PerAppThemingWindow.class); - startService(intent); - finish(); - } - - private void requestSystemWindowPermission() { - Intent intent = new Intent (Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getPackageName())); - startActivityForResult(intent, REQUEST_SYSTEM_WINDOW_PERMISSION); - } -} diff --git a/src/com/cyngn/theme/chooser/ComponentCardView.java b/src/com/cyngn/theme/chooser/ComponentCardView.java deleted file mode 100644 index 24eb58c..0000000 --- a/src/com/cyngn/theme/chooser/ComponentCardView.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.IntEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOverlay; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class ComponentCardView extends LinearLayout { - public static final int CARD_FADE_DURATION = 300; - - private static final float SEMI_OPAQUE_ALPHA = 0.4f; - - protected TextView mLabel; - protected View mEmptyView; - protected View mContentView; - - // Expanded Padding - int mExpandPadLeft; - int mExpandPadTop; - int mExpandPadRight; - int mExpandPadBottom; - - // The background drawable is returning an alpha of 0 regardless of what we set it to - // so this will help us keep track of what the drawable's alpha is at. - private int mBackgroundAlpha = 255; - - public ComponentCardView(Context context) { - this(context, null); - } - - public ComponentCardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ComponentCardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setEnabled(false); - } - - @Override - protected void onFinishInflate() { - mLabel = (TextView) findViewById(R.id.label); - - Resources r = getContext().getResources(); - mExpandPadLeft = - (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingLeft(); - mExpandPadTop = - (int) r.getDimension(R.dimen.card_padding_top) + getPaddingTop(); - mExpandPadRight = - (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingRight(); - mExpandPadBottom = - (int) r.getDimension(R.dimen.card_padding_bottom) + getPaddingBottom(); - mEmptyView = findViewById(R.id.empty); - mContentView = findViewById(R.id.content); - } - - public void expand(boolean showLabel) { - setEnabled(true); - TransitionDrawable bg = null; - if (getBackground() instanceof TransitionDrawable) { - bg = (TransitionDrawable) getBackground(); - } - if (bg != null) { - Rect paddingRect = new Rect(); - bg.getPadding(paddingRect); - } - - setPadding(mExpandPadLeft, 0, mExpandPadRight, mExpandPadBottom); - - if (mLabel != null) { - mLabel.setAlpha(showLabel ? 1f : 0f); - mLabel.setVisibility(View.VISIBLE); - } - } - - public void animateExpand() { - if (getBackground() instanceof TransitionDrawable) { - TransitionDrawable background = (TransitionDrawable) getBackground(); - if (mLabel != null) { - mLabel.setVisibility(View.VISIBLE); - mLabel.setAlpha(0f); - mLabel.animate().alpha(1f).setDuration(CARD_FADE_DURATION).start(); - } - background.startTransition(CARD_FADE_DURATION); - } - } - - public void collapse() { - setEnabled(false); - if (mLabel != null) { - mLabel.setVisibility(View.GONE); - } - setPadding(0, 0, 0, 0); - } - - public void animateFadeOut() { - if (mLabel != null) { - mLabel.animate().alpha(0f).setDuration(CARD_FADE_DURATION); - } - - if (getBackground() instanceof TransitionDrawable) { - TransitionDrawable background = (TransitionDrawable) getBackground(); - background.reverseTransition(CARD_FADE_DURATION); - } - } - - /** - * Animates the card background and the title to 20% opacity. - */ - public void animateCardFadeOut() { - this.animate().alpha(SEMI_OPAQUE_ALPHA).setDuration(CARD_FADE_DURATION); - } - - /** - * Animates the card background and the title back to full opacity. - */ - public void animateCardFadeIn() { - this.animate().alpha(1f).setDuration(CARD_FADE_DURATION); - } - - /** - * Animates a change in the content of the card - * @param v View in card to animate - * @param overlay Drawable to animate as a ViewOverlay - * @param duration Duration of animation - */ - public void animateContentChange(View v, final Drawable overlay, long duration) { - final ViewOverlay viewOverlay = this.getOverlay(); - viewOverlay.add(overlay); - final int x = getRelativeLeft(v); - final int y = getRelativeTop(v); - final int width = v.getWidth(); - final int height = v.getHeight(); - overlay.setBounds(x, y, x + v.getWidth(), y + v.getHeight()); - - final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); - overlayAnimator.setDuration(duration); - overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (Float) animation.getAnimatedValue(); - overlay.setAlpha((int) (255 * value)); - int newWidth = (int) (value * width); - int newHeight = (int) (value * height); - int dw = (width - newWidth) / 2; - int dh = (height - newHeight) / 2; - overlay.setBounds(x + dw, y + dh, x + dw + newWidth, y + dh + newHeight); - } - }); - overlayAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - // Clear out the ViewOverlay now that we are done animating - viewOverlay.clear(); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }); - - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(v, "alpha", 0f, 1f)) - .with(ObjectAnimator.ofFloat(v, "scaleX", 0f, 1f)) - .with(ObjectAnimator.ofFloat(v, "scaleY", 0f, 1f)); - set.setDuration(duration); - - set.start(); - overlayAnimator.start(); - } - - public void setEmptyViewEnabled(boolean enabled) { - if (mEmptyView != null) { - mEmptyView.setVisibility(enabled ? View.VISIBLE : View.GONE); - } - if (mContentView != null) { - mContentView.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); - } - } - - public boolean isShowingEmptyView() { - return mEmptyView != null && mEmptyView.getVisibility() == View.VISIBLE; - } - - private int getRelativeTop(View v) { - if (v.getParent() == this) { - return v.getTop(); - } else { - return v.getTop() + ((View) v.getParent()).getTop(); - } - } - - private int getRelativeLeft(View v) { - if (v.getParent() == this) { - return v.getLeft(); - } else { - return v.getLeft() + ((View) v.getParent()).getLeft(); - } - } -} diff --git a/src/com/cyngn/theme/chooser/ComponentSelector.java b/src/com/cyngn/theme/chooser/ComponentSelector.java deleted file mode 100644 index 5347250..0000000 --- a/src/com/cyngn/theme/chooser/ComponentSelector.java +++ /dev/null @@ -1,920 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.AnimationUtils; -import android.widget.ComponentSelectorLinearLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import com.cyngn.theme.util.AudioUtils; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.ThemedTypefaceHelper; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; - -import java.util.Map; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_FONT; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; - -public class ComponentSelector extends LinearLayout - implements LoaderManager.LoaderCallbacks { - private static final String TAG = ComponentSelector.class.getSimpleName(); - - public static final boolean DEBUG_SELECTOR = false; - - public static final String EXTERNAL_WALLPAPER = "external"; - - public static final String MOD_LOCK = "mod_lock"; - - private static final int EXTRA_WALLPAPER_COMPONENTS = 2; - - private static final int EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS = 3; - - protected static final long DEFAULT_COMPONENT_ID = 0; - - private Context mContext; - private LayoutInflater mInflater; - private ComponentSelectorLinearLayout mContent; - private LinearLayout.LayoutParams mItemParams; - private LinearLayout.LayoutParams mSoundItemParams = - new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1.0f); - - private String mComponentType; - private int mBatteryStyle; - private int mItemsPerPage; - private String mAppliedComponentPkgName; - private String mSelectedComponentPkgName; - private long mSelectedComponentId; - - // animations for bringing selector in and out of view - private Animation mAnimateIn; - private Animation mAnimateOut; - - private OnItemClickedListener mListener; - - private OnOpenCloseListener mOpenCloseListener; - - private MediaPlayer mMediaPlayer; - private ImageView mCurrentPlayPause; - - private TypefaceHelperCache mTypefaceCache; - - private ThemesObserver mThemesObserver; - - public static final String IS_LIVE_LOCK_SCREEN_VIEW = "is_live_lock_screen_view"; - private View mPrevLockScreenView; - - public ComponentSelector(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ComponentSelector); - mItemsPerPage = a.getInt(R.styleable.ComponentSelector_itemsPerPage, - context.getResources().getInteger(R.integer.default_items_per_page)); - a.recycle(); - - mContext = context; - mInflater = LayoutInflater.from(context); - // TODO: Load from settings once available - mBatteryStyle = 0; /*Settings.System.getInt(context.getContentResolver(), - Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ - - mAnimateIn = AnimationUtils.loadAnimation(mContext, - R.anim.component_selection_animate_in); - mAnimateOut = AnimationUtils.loadAnimation(mContext, - R.anim.component_selection_animate_out); - mAnimateIn.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - if (mOpenCloseListener != null) mOpenCloseListener.onSelectorOpened(); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mAnimateOut.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - if (mOpenCloseListener != null) mOpenCloseListener.onSelectorClosing(); - } - - @Override - public void onAnimationEnd(Animation animation) { - setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - if (mCurrentPlayPause != null) { - mCurrentPlayPause.setImageResource(R.drawable.media_sound_selector_preview); - mCurrentPlayPause = null; - } - } - }); - mTypefaceCache = TypefaceHelperCache.getInstance(); - mThemesObserver = new ThemesObserver(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContent = (ComponentSelectorLinearLayout) findViewById(R.id.content); - setEnabled(false); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mThemesObserver.register(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mThemesObserver.unregister(); - } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - // Window visibility transitions as follows - // VISIBLE -> INVISIBLE -> GONE - // GONE -> INVISIBLE -> VISIBLE - if (visibility == View.GONE) { - mThemesObserver.unregister(); - } else if (visibility == View.VISIBLE) { - mThemesObserver.register(); - } - } - - public void resetComponentType() { - mComponentType = null; - } - - public void setComponentType(String component) { - setComponentType(component, null, DEFAULT_COMPONENT_ID); - } - - public void setComponentType(String component, String selectedPkgName) { - setComponentType(component, null, DEFAULT_COMPONENT_ID); - } - - public void setComponentType(String component, String selectedPkgName, - long selectedComponentId) { - mSelectedComponentPkgName = selectedPkgName; - mSelectedComponentId = selectedComponentId; - if (mComponentType == null || !mComponentType.equals(component)) { - mContent.removeAllViews(); - mComponentType = component; - ((FragmentActivity) mContext).getSupportLoaderManager().restartLoader( - getLoaderIdFromComponent(component), null, this); - } else if (mComponentType != null) { - int count = mContent.getChildCount(); - final Resources res = getResources(); - boolean highlightTitle; - Map selectedComponents = null; - if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { - selectedComponents = ((ChooserActivity)mContext) - .getSelectedComponentsMap(); - } - for (int i = 0; i < count; i++) { - final View child = mContent.getChildAt(i); - final TextView tv = (TextView) child.findViewById(R.id.title); - // The child itself may have the tag, or in the case of sounds its the grandchild, - // either way the parent of the textview will have the pkgName in its tag - final View viewWithTag = (View) tv.getParent(); - final String pkgName = (String) viewWithTag.getTag(R.id.tag_key_package_name); - Long cmpntId = (Long) viewWithTag.getTag(R.id.tag_key_component_id); - if (cmpntId == null) { - cmpntId = DEFAULT_COMPONENT_ID; - } - Boolean isLLS = (Boolean) viewWithTag.getTag(R.id.tag_key_live_lock_screen); - highlightTitle = false; - if (selectedComponents != null && isLLS != null) { - if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) - && !isLLS) || (TextUtils.equals(selectedComponents.get( - MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS)) { - highlightTitle = true; - } - } else if (pkgName.equals(selectedPkgName) && cmpntId == selectedComponentId) { - highlightTitle = true; - } - if (highlightTitle) { - tv.setTextColor(res.getColor(R.color.component_selection_current_text_color)); - } else { - tv.setTextColor(res.getColor(android.R.color.white)); - } - } - } - } - - public String getComponentType() { - return mComponentType; - } - - public void setNumItemsPerPage(int itemsPerPage) { - if (mItemsPerPage != itemsPerPage) { - mItemsPerPage = itemsPerPage; - } - } - - public void setHeight(int height) { - ViewGroup.LayoutParams params = mContent.getLayoutParams(); - if (params.height != height) { - params.height = height; - mContent.setLayoutParams(params); - requestLayout(); - } - } - - public void show(String componentType, int itemsPerPage, int height) { - show(componentType, null, itemsPerPage, height); - } - - public void show(String componentType, String selectedPkgName, int itemsPerPage, int height) { - show(componentType, selectedPkgName, DEFAULT_COMPONENT_ID, itemsPerPage, height); - } - - public void show(String componentType, String selectedPkgName, long selectedComponentId, - int itemsPerPage, int height) { - setNumItemsPerPage(itemsPerPage); - setHeight(height); - setComponentType(componentType, selectedPkgName, selectedComponentId); - show(); - } - - public void show() { - if (getVisibility() == View.GONE) { - setEnabled(true); - setVisibility(View.VISIBLE); - startAnimation(mAnimateIn); - } - } - - public void hide() { - if (getVisibility() == View.VISIBLE && isEnabled()) { - setEnabled(false); - startAnimation(mAnimateOut); - } - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) mMediaPlayer.stop(); - } - - private int getLoaderIdFromComponent(String component) { - if (MODIFIES_STATUS_BAR.equals(component)) { - return LOADER_ID_STATUS_BAR; - } - if (MODIFIES_NAVIGATION_BAR.equals(component)) { - return LOADER_ID_NAVIGATION_BAR; - } - if (MODIFIES_FONTS.equals(component)) { - return LOADER_ID_FONT; - } - if (MODIFIES_ICONS.equals(component)) { - return LOADER_ID_ICONS; - } - if (MODIFIES_OVERLAYS.equals(component)) { - return LOADER_ID_STYLE; - } - if (MODIFIES_LAUNCHER.equals(component)) { - return LOADER_ID_WALLPAPER; - } - if (MODIFIES_BOOT_ANIM.equals(component)) { - return LOADER_ID_BOOT_ANIMATION; - } - if (MODIFIES_RINGTONES.equals(component)) { - return LOADER_ID_RINGTONE; - } - if (MODIFIES_NOTIFICATIONS.equals(component)) { - return LOADER_ID_NOTIFICATION; - } - if (MODIFIES_ALARMS.equals(component)) { - return LOADER_ID_ALARM; - } - if (MODIFIES_LOCKSCREEN.equals(component)) { - return LOADER_ID_LOCKSCREEN; - } - return -1; - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return CursorLoaderHelper.componentSelectorCursorLoader(mContext, id); - } - - @Override - public void onLoadFinished(Loader loader, final Cursor data) { - int currentLoaderId = loader.getId(); - int count = data.getCount(); - int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; - final Resources res = getResources(); - int dividerPadding = res.getDimensionPixelSize(R.dimen.component_divider_padding_top); - int dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height); - MatrixCursor lockScreenMatrixCursor = null; - - switch (currentLoaderId) { - case LOADER_ID_ALARM: - case LOADER_ID_NOTIFICATION: - case LOADER_ID_RINGTONE: - mItemParams = new LayoutParams(screenWidth, - ViewGroup.LayoutParams.MATCH_PARENT); - // Sounds are a special case where there are two items laid out - // vertically. This layout is added as a single item so we need to - // adjust the count by dividing by the number of items per page and - // rounding up so we include all items. - count = (int) Math.ceil((double)count / mItemsPerPage); - mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); - break; - case LOADER_ID_BOOT_ANIMATION: - dividerPadding = res.getDimensionPixelSize( - R.dimen.component_divider_padding_top_bootani); - dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height_bootani); - // fall through to default - default: - mItemParams = new LayoutParams(screenWidth / mItemsPerPage, - ViewGroup.LayoutParams.MATCH_PARENT); - if (currentLoaderId == LOADER_ID_WALLPAPER || - currentLoaderId == LOADER_ID_LOCKSCREEN) { - count += EXTRA_WALLPAPER_COMPONENTS; - } - - if (currentLoaderId == LOADER_ID_LOCKSCREEN) { - lockScreenMatrixCursor = splitLockScreenCursor(data); - if (lockScreenMatrixCursor != null) { - count = lockScreenMatrixCursor.getCount() + EXTRA_WALLPAPER_COMPONENTS; - } - } - - mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); - break; - } - - mContent.setDividerPadding(dividerPadding); - mContent.setDividerHeight(dividerHeight); - - new LoadItemsTask().execute((lockScreenMatrixCursor != null) - ? lockScreenMatrixCursor : data, count); - } - - /* Some themes might contain a lock wallpaper AND a live lock screen. - * ThemesProvider will return one single row containing both thumbnail paths - * (ThemesProvider groups by theme_id) so we need to create a cursor on the - * fly to split that row into 2 to properly generate a view for each thumbnail - */ - private MatrixCursor splitLockScreenCursor(Cursor data) { - int lockWallPaperThumbnailIndx, llsThumbnailIndx, pkgIndx; - String lockWallPaperThumbnail, liveLockScreenThumbnail, pkgName; - MatrixCursor lockScreenMatrixCursor; - int needToSplitRowAt = -1; - - lockWallPaperThumbnailIndx = data.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); - llsThumbnailIndx = data.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL); - pkgIndx = data.getColumnIndex(ThemesColumns.PKG_NAME); - - if (lockWallPaperThumbnailIndx < 0 || llsThumbnailIndx < 0 || pkgIndx < 0) { - //An invalid cursor was provided, we can't continue processing - Log.e(TAG, "Failed to process cursor due to missing columns"); - return null; - } - - //Let's find out if we really need to allocate a MatrixCursor. - //If we find at least one row with valid data in lock_wallpaper_thumbnail AND - //live_lock_screen_thumbnail it means we do. - data.moveToPosition(-1); - while (data.moveToNext()) { - lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); - liveLockScreenThumbnail = data.getString(llsThumbnailIndx); - if (!TextUtils.isEmpty(lockWallPaperThumbnail) - && !TextUtils.isEmpty(liveLockScreenThumbnail)) { - needToSplitRowAt = data.getPosition(); - break; - } - } - - if (needToSplitRowAt == -1) return null; - - lockScreenMatrixCursor = new MatrixCursor(data.getColumnNames()); - //Clone all the *regular* rows up to needToSplitRowAt - for (int indx = 0; indx < needToSplitRowAt; indx++) { - data.moveToPosition(indx); - lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); - } - if (needToSplitRowAt == 0) { - data.moveToPosition(-1); - } - while (data.moveToNext()) { - lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); - liveLockScreenThumbnail = data.getString(llsThumbnailIndx); - if (!TextUtils.isEmpty(lockWallPaperThumbnail) - && !TextUtils.isEmpty(liveLockScreenThumbnail)) { - pkgName = data.getString(pkgIndx); - - MatrixCursor.RowBuilder lockWallpaperRow = lockScreenMatrixCursor.newRow(); - MatrixCursor.RowBuilder liveLockScreenRow = lockScreenMatrixCursor.newRow(); - - for (String col : data.getColumnNames()) { - if (TextUtils.equals(col, PreviewColumns.LOCK_WALLPAPER_THUMBNAIL)) { - lockWallpaperRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - lockWallPaperThumbnail); - liveLockScreenRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, null); - } else if (TextUtils.equals(col, PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL)) { - lockWallpaperRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, null); - liveLockScreenRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - liveLockScreenThumbnail); - } else if (TextUtils.equals(col, MODIFIES_LIVE_LOCK_SCREEN)) { - lockWallpaperRow.add(MODIFIES_LIVE_LOCK_SCREEN, 0); - liveLockScreenRow.add(MODIFIES_LIVE_LOCK_SCREEN, 1); - } else { - int colIndx = data.getColumnIndex(col); - lockWallpaperRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, - colIndx)); - liveLockScreenRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, - colIndx)); - } - } - } else { - //This is a regular row, so just clone it - lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); - } - } - return lockScreenMatrixCursor; - } - - @Override - public void onLoaderReset(Loader loader) { - } - - public void setOnItemClickedListener(OnItemClickedListener listener) { - mListener = listener; - } - - public void setOnOpenCloseListener(OnOpenCloseListener listener) { - mOpenCloseListener = listener; - } - - private View newView(Cursor cursor, int position, ViewGroup container) { - if (MODIFIES_STATUS_BAR.equals(mComponentType)) { - return newStatusBarView(cursor, container, position); - } - if (MODIFIES_NAVIGATION_BAR.equals(mComponentType)) { - return newNavBarView(cursor, container, position); - } - if (MODIFIES_FONTS.equals(mComponentType)) { - return newFontView(cursor, container, position); - } - if (MODIFIES_ICONS.equals(mComponentType)) { - return newIconView(cursor, container, position); - } - if (MODIFIES_OVERLAYS.equals(mComponentType)) { - return newStyleView(cursor, container, position); - } - if (MODIFIES_LAUNCHER.equals(mComponentType)) { - return newWallpapersView(cursor, container, position, - cursor.getColumnIndex(PreviewColumns.WALLPAPER_THUMBNAIL), false, - EXTRA_WALLPAPER_COMPONENTS); - } - if (MODIFIES_BOOT_ANIM.equals(mComponentType)) { - return newBootanimationView(cursor, container, position); - } - if (MODIFIES_RINGTONES.equals(mComponentType) || - MODIFIES_NOTIFICATIONS.equals(mComponentType) || - MODIFIES_ALARMS.equals(mComponentType)) { - return newSoundView(cursor, container, position, mComponentType); - } - if (MODIFIES_LOCKSCREEN.equals(mComponentType)) { - boolean isLiveLockScreen = false; - if (position >= EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS) { - cursor.moveToPosition(position - EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); - int liveLockIndex = cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); - isLiveLockScreen = liveLockIndex >= 0 && - cursor.getInt(liveLockIndex) == 1; - } - int index = isLiveLockScreen - ? cursor.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL) - : cursor.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); - return newWallpapersView(cursor, container, position, index, isLiveLockScreen, - EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); - } - return null; - } - - private View newStatusBarView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.status_bar_component_selection_item, - parent, false); - int wifiIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); - int signalIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); - int bluetoothIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); - int batteryIndex = cursor.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); - int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.slot1)).setImageBitmap( - Utils.loadBitmapBlob(cursor, wifiIndex)); - ((ImageView) v.findViewById(R.id.slot2)).setImageBitmap( - Utils.loadBitmapBlob(cursor, signalIndex)); - ((ImageView) v.findViewById(R.id.slot3)).setImageBitmap( - Utils.loadBitmapBlob(cursor, bluetoothIndex)); - ((ImageView) v.findViewById(R.id.slot4)).setImageBitmap( - Utils.loadBitmapBlob(cursor, batteryIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.findViewById(R.id.container).setBackground( - new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newNavBarView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.navigation_bar_component_selection_item, parent, - false); - int backIndex = cursor.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); - int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.back)).setImageBitmap( - Utils.loadBitmapBlob(cursor, backIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.findViewById(R.id.container).setBackground( - new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newFontView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.font_component_selection_item, parent, false); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - TextView preview = (TextView) v.findViewById(R.id.text_preview); - String pkgName = cursor.getString(pkgNameIndex); - - ThemedTypefaceHelper helper = mTypefaceCache.getHelperForTheme(mContext, pkgName); - Typeface typefaceNormal = helper.getTypeface(Typeface.NORMAL); - preview.setTypeface(typefaceNormal); - - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newIconView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, - false); - int iconIndex = cursor.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( - Utils.loadBitmapBlob(cursor, iconIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newStyleView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, - false); - int styleIndex = cursor.getColumnIndex(PreviewColumns.STYLE_THUMBNAIL); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( - Utils.loadBitmapBlob(cursor, styleIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newWallpapersView(Cursor cursor, ViewGroup parent, int position, - int wallpaperIndex, boolean isLiveLockScreen, int numExtraComponents) { - View v = mInflater.inflate(R.layout.wallpaper_component_selection_item, parent, - false); - ImageView iv = (ImageView) v.findViewById(R.id.icon); - if (position == 0) { - iv.setImageResource(R.drawable.img_wallpaper_none); - v.setTag(R.id.tag_key_package_name, ""); - ((TextView) v.findViewById(R.id.title)).setText(R.string.wallpaper_none_title); - } else if (position == 1) { - iv.setImageResource(R.drawable.img_wallpaper_external); - v.setTag(R.id.tag_key_package_name, EXTERNAL_WALLPAPER); - ((TextView) v.findViewById(R.id.title)) - .setText(R.string.wallpaper_external_title); - } else if (numExtraComponents == EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS && position == 2) { - // TODO: update drawable once the asset is provided by design - iv.setImageResource(android.R.drawable.ic_lock_lock); - v.setTag(R.id.tag_key_package_name, MOD_LOCK); - ((TextView) v.findViewById(R.id.title)) - .setText(R.string.mod_lock_title); - } else { - cursor.moveToPosition(position - numExtraComponents); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); - long cmpntId = (cmpntIdIndex >= 0) ? - cursor.getLong(cmpntIdIndex) : DEFAULT_COMPONENT_ID; - iv.setImageBitmap( - Utils.loadBitmapBlob(cursor, wallpaperIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setTag(R.id.tag_key_component_id, cmpntId); - v.setTag(R.id.tag_key_live_lock_screen, isLiveLockScreen); - v.findViewById(R.id.live_lock_screen_badge) - .setVisibility(isLiveLockScreen ? View.VISIBLE : View.GONE); - } - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newBootanimationView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.bootani_component_selection_item, parent, - false); - int wallpaperIndex = cursor.getColumnIndex(PreviewColumns.BOOTANIMATION_THUMBNAIL); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.preview)).setImageBitmap( - Utils.loadBitmapBlob(cursor, wallpaperIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newSoundView(Cursor cursor, ViewGroup parent, int position, - final String component) { - LinearLayout container = (LinearLayout) mInflater.inflate( - R.layout.component_selection_sounds_pager_item, parent, false); - container.setWeightSum(mItemsPerPage); - for (int i = 0; i < mItemsPerPage; i++) { - int index = position * mItemsPerPage + i; - if (cursor.getCount() <= index) continue; - cursor.moveToPosition(index); - View v = mInflater.inflate(R.layout.sound_component_selection_item, parent, - false); - final int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - container.addView(v, mSoundItemParams); - final View playButton = v.findViewById(R.id.play_button); - playButton.setTag(cursor.getString(pkgNameIndex)); - playButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - int type; - String pkgName = (String) v.getTag(); - if (component.equals(MODIFIES_RINGTONES)) { - type = RingtoneManager.TYPE_RINGTONE; - } else if (component.equals(MODIFIES_NOTIFICATIONS)) { - type = RingtoneManager.TYPE_NOTIFICATION; - } else { - type = RingtoneManager.TYPE_ALARM; - } - boolean shouldStop = playButton == mCurrentPlayPause; - try { - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { - mMediaPlayer.stop(); - if (mCurrentPlayPause != null) { - mCurrentPlayPause.setImageResource( - R.drawable.media_sound_selector_preview); - } - mCurrentPlayPause = null; - } - if (mCurrentPlayPause != playButton && !shouldStop) { - AudioUtils.loadThemeAudible(mContext, type, pkgName, - mMediaPlayer); - mMediaPlayer.start(); - mCurrentPlayPause = (ImageView) playButton; - mCurrentPlayPause.setImageResource( - R.drawable.media_sound_selector_stop); - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to play preview sound", e); - } - } - }); - } - - return container; - } - - private class LoadItemsTask extends AsyncTask { - - @Override - protected Void doInBackground(Object... params) { - Cursor c = (Cursor) params[0]; - int count = (Integer) params[1]; - for (int i = 0; i < count && !isCancelled(); i++) { - final View v = newView(c, i, mContent); - mContent.post(new Runnable() { - @Override - public void run() { - mContent.addView(v, mItemParams); - } - }); - } - - if (c instanceof MatrixCursor) { - c.close(); - } - - // destroy the loader now that we are done with it - ComponentSelector.this.post(new Runnable() { - @Override - public void run() { - ((FragmentActivity)mContext).getSupportLoaderManager().destroyLoader( - getLoaderIdFromComponent(mComponentType)); - } - }); - return null; - } - } - - private void setTitle(TextView titleView, Cursor cursor) { - String pkgName = cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)); - int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); - long cmpntId = DEFAULT_COMPONENT_ID; - if (cmpntIdIndex >= 0) { - cmpntId = cursor.getLong(cmpntIdIndex); - } - if (Utils.getDefaultThemePackageName(mContext).equals(pkgName)) { - titleView.setText(mContext.getString(R.string.default_tag_text)); - titleView.setTypeface(titleView.getTypeface(), Typeface.BOLD); - } else { - titleView.setText(cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); - } - boolean highlightTitle = false; - if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { - Map selectedComponents = ((ChooserActivity)mContext) - .getSelectedComponentsMap(); - int isLLS = cursor.getInt(cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN)); - if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) - && isLLS == 0) || (TextUtils.equals( - selectedComponents.get(MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS == 1)) { - highlightTitle = true; - } - } else if (pkgName.equals(mSelectedComponentPkgName) && cmpntId == mSelectedComponentId) { - highlightTitle = true; - } - if (highlightTitle) { - titleView.setTextColor(getResources().getColor( - R.color.component_selection_current_text_color)); - } - } - - private OnClickListener mItemClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - long cmpntId = DEFAULT_COMPONENT_ID; - String pkgName = (String) v.getTag(R.id.tag_key_package_name); - Long cmpntIdTag = (Long) v.getTag(R.id.tag_key_component_id); - if (cmpntIdTag != null) { - cmpntId = cmpntIdTag; - } - Boolean isLiveLock = (Boolean) v.getTag(R.id.tag_key_live_lock_screen); - boolean isSamePkgButDifferentLockScreen = false; - Bundle params = null; - if (isLiveLock != null) { - params = new Bundle(); - params.putBoolean(IS_LIVE_LOCK_SCREEN_VIEW, isLiveLock); - - if (pkgName.equals(mSelectedComponentPkgName) && v != mPrevLockScreenView) { - isSamePkgButDifferentLockScreen = true; - } - mPrevLockScreenView = v; - } - if (DEBUG_SELECTOR) Toast.makeText(mContext, pkgName, Toast.LENGTH_SHORT).show(); - if (mListener != null && (isSamePkgButDifferentLockScreen || - !pkgName.equals(mSelectedComponentPkgName) || - pkgName.equals(EXTERNAL_WALLPAPER) || cmpntId != mSelectedComponentId)) { - mSelectedComponentPkgName = pkgName; - mSelectedComponentId = cmpntId; - mListener.onItemClicked(pkgName, cmpntId, params); - final int count = mContent.getChildCount(); - final Resources res = getResources(); - for (int i = 0; i < count; i++) { - final View child = mContent.getChildAt(i); - final TextView tv = (TextView) child.findViewById(R.id.title); - if (tv != null) { - if (child == v) { - tv.setTextColor( - res.getColor(R.color.component_selection_current_text_color)); - } else { - tv.setTextColor(res.getColor(android.R.color.white)); - } - } - } - } - } - }; - - private class ThemesObserver extends ContentObserver { - public ThemesObserver() { - super(null); - } - - public void register() { - mContext.getContentResolver().registerContentObserver( - ThemesColumns.CONTENT_URI, false, this); - } - - public void unregister() { - mContext.getContentResolver().unregisterContentObserver(this); - } - - @Override - public void onChange(boolean selfChange) { - // reload items by calling setComponentType() - if (mComponentType != null) { - final String componentType = mComponentType; - mComponentType = null; - mContent.post(new Runnable() { - @Override - public void run() { - setComponentType(componentType, mSelectedComponentPkgName); - } - }); - } - } - } - - public interface OnItemClickedListener { - public void onItemClicked(String pkgName, long componentId, Bundle params); - } - - public interface OnOpenCloseListener { - public void onSelectorOpened(); - public void onSelectorClosed(); - public void onSelectorClosing(); - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/IconTransitionDrawable.java b/src/com/cyngn/theme/chooser/IconTransitionDrawable.java deleted file mode 100644 index 8079fc2..0000000 --- a/src/com/cyngn/theme/chooser/IconTransitionDrawable.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * Copyright (C) 2014 The Cyanogen, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.cyngn.theme.chooser; - -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.os.SystemClock; - -/** - * An extension of LayerDrawables that is intended to cross-fade between - * the first and second layer. To start the transition, call {@link #startTransition(int)}. To - * display just the first layer, call {@link #resetTransition()}. - *

- * It can be defined in an XML file with the <transition> element. - * Each Drawable in the transition is defined in a nested <item>. For more - * information, see the guide to Drawable Resources.

- * - * @attr ref android.R.styleable#LayerDrawableItem_left - * @attr ref android.R.styleable#LayerDrawableItem_top - * @attr ref android.R.styleable#LayerDrawableItem_right - * @attr ref android.R.styleable#LayerDrawableItem_bottom - * @attr ref android.R.styleable#LayerDrawableItem_drawable - * @attr ref android.R.styleable#LayerDrawableItem_id - * - */ -public class IconTransitionDrawable extends LayerDrawable { - - /** - * A transition is about to start. - */ - private static final int TRANSITION_STARTING = 0; - - /** - * The transition has started and the animation is in progress - */ - private static final int TRANSITION_RUNNING = 1; - - /** - * No transition will be applied - */ - private static final int TRANSITION_NONE = 2; - - /** - * The current state of the transition. One of {@link #TRANSITION_STARTING}, - * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE} - */ - private int mTransitionState = TRANSITION_NONE; - - private long mStartTimeMillis; - private int mFrom; - private int mTo; - private int mDuration; - private int mAlpha = 0; - private float mFromScale; - private float mToScale; - - /** - * Create a new transition drawable with the specified list of layers. At least - * 2 layers are required for this drawable to work properly. - */ - public IconTransitionDrawable(Drawable[] layers) { - super(layers); - } - - /** - * Begin the second layer on top of the first layer. - * - * @param durationMillis The length of the transition in milliseconds - */ - public void startTransition(int durationMillis) { - mFrom = 0; - mTo = 255; - mAlpha = 0; - mFromScale = 0f; - mToScale = 1.0f; - mDuration = durationMillis; - mTransitionState = TRANSITION_STARTING; - invalidateSelf(); - } - - /** - * Show only the first layer. - */ - public void resetTransition() { - mAlpha = 0; - mTransitionState = TRANSITION_NONE; - invalidateSelf(); - } - - @Override - public void draw(Canvas canvas) { - boolean done = true; - float scale = 0f; - - switch (mTransitionState) { - case TRANSITION_STARTING: - mStartTimeMillis = SystemClock.uptimeMillis(); - done = false; - mTransitionState = TRANSITION_RUNNING; - break; - - case TRANSITION_RUNNING: - if (mStartTimeMillis >= 0) { - float normalized = (float) - (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration; - done = normalized >= 1.0f; - normalized = Math.min(normalized, 1.0f); - mAlpha = (int) (mFrom + (mTo - mFrom) * normalized); - scale = mFromScale + (mToScale - mFromScale) * normalized; - } - break; - } - - final int alpha = mAlpha; - - if (done) { - // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw - // the appropriate drawable[s] and return - if (alpha == 0) { - getDrawable(0).draw(canvas); - } - if (alpha == 0xFF) { - getDrawable(1).draw(canvas); - - } - return; - } - - Drawable d; - d = getDrawable(0); - d.setAlpha(255 - alpha); - int cx = getIntrinsicWidth() / 2; - int cy = getIntrinsicHeight() / 2; - canvas.save(); - canvas.scale(1.0f - scale, 1.0f - scale, cx, cy); - d.draw(canvas); - canvas.restore(); - d.setAlpha(0xFF); - - if (alpha > 0) { - d = getDrawable(1); - d.setAlpha(alpha); - canvas.save(); - canvas.scale(scale, scale, cx, cy); - d.draw(canvas); - canvas.restore(); - d.setAlpha(0xFF); - } - - if (!done) { - invalidateSelf(); - } - } -} diff --git a/src/com/cyngn/theme/chooser/MyThemeFragment.java b/src/com/cyngn/theme/chooser/MyThemeFragment.java deleted file mode 100644 index 8306e55..0000000 --- a/src/com/cyngn/theme/chooser/MyThemeFragment.java +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.app.WallpaperManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.MediaPlayer; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.content.Loader; -import android.util.Log; -import android.util.MutableLong; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import com.cyngn.theme.util.AudioUtils; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.ThemedTypefaceHelper; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeChangeRequest; -import cyanogenmod.themes.ThemeChangeRequest.RequestType; -import cyanogenmod.themes.ThemeManager; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALL; - -public class MyThemeFragment extends ThemeFragment { - private static final String TAG = MyThemeFragment.class.getSimpleName(); - - private static final String ARG_BASE_THEME_PACKAGE_NAME = "baseThemePkgName"; - private static final String ARG_BASE_THEME_NAME = "baseThemeName"; - private static final String ARG_BASE_THEME_AUTHOR = "baseThemeAuthor"; - - private String mBaseThemeName; - private String mBaseThemeAuthor; - - private SurfaceView mSurfaceView; - - static MyThemeFragment newInstance(String baseThemePkgName, String baseThemeName, - String baseThemeAuthor, boolean skipLoadingAnim, - boolean animateToLockScreenCard) { - MyThemeFragment f = new MyThemeFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, CURRENTLY_APPLIED_THEME); - args.putString(ARG_BASE_THEME_PACKAGE_NAME, baseThemePkgName); - args.putString(ARG_BASE_THEME_NAME, baseThemeName); - args.putString(ARG_BASE_THEME_AUTHOR, baseThemeAuthor); - args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); - args.putBoolean(ARG_ANIMATE_TO_LOCK_SCREEN_CARD, animateToLockScreenCard); - f.setArguments(args); - return f; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Context context = getActivity(); - ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, - getAppliedFontPackageName()); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - mBaseThemePkgName = getArguments().getString(ARG_BASE_THEME_PACKAGE_NAME); - mBaseThemeName = getArguments().getString(ARG_BASE_THEME_NAME); - mBaseThemeAuthor = getArguments().getString(ARG_BASE_THEME_AUTHOR); - mShowLockScreenSelectorAfterContentLoaded = getArguments().getBoolean( - ARG_ANIMATE_TO_LOCK_SCREEN_CARD); - mSurfaceView = createSurfaceView(); - populateBaseThemeSupportedComponents(mBaseThemePkgName); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = super.onCreateView(inflater, container, savedInstanceState); - mThemeTagLayout.setAppliedTagEnabled(true); - if (mBaseThemePkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { - mThemeTagLayout.setDefaultTagEnabled(true); - } - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - mDelete.setVisibility(View.GONE); - setCustomized(isThemeCustomized()); - return v; - } - - @Override - public void onResume() { - super.onResume(); - if (!mExpanded && getLoaderManager().getLoader(0) != null) { - getLoaderManager().restartLoader(0, null, this); - } - - IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - getActivity().registerReceiver(mWallpaperChangeReceiver, filter); - } - - @Override - public void onPause() { - getActivity().unregisterReceiver(mWallpaperChangeReceiver); - super.onPause(); - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (mThemeTagLayout == null) return; - - if (!isVisibleToUser) { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - } else { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { - PreferenceUtils.removeUpdatedTheme(getActivity(), mBaseThemePkgName); - } - } - } - - @Override - protected boolean onPopupMenuItemClick(MenuItem item) { - switch(item.getItemId()) { - case R.id.menu_reset: - resetTheme(); - return true; - } - - return super.onPopupMenuItemClick(item); - } - - @Override - public void collapse(boolean applyTheme) { - super.collapse(applyTheme); - if (mSurfaceView != null) mSurfaceView.setVisibility(View.VISIBLE); - } - - @Override - public void expand() { - super.expand(); - if (mSurfaceView != null && mShadowFrame.indexOfChild(mSurfaceView) >= 0) { - mSurfaceView.setVisibility(View.GONE); - mWallpaper.setVisibility(View.INVISIBLE); - } - } - - @Override - public void performClick(boolean clickedOnContent) { - if (clickedOnContent) { - showCustomizeResetLayout(); - } else { - if (isShowingCustomizeResetLayout()) { - hideCustomizeResetLayout(); - } else { - super.performClick(clickedOnContent); - } - } - } - - @Override - public void setCurrentTheme(Map currentTheme, - MutableLong currentWallpaperComponentId) { - super.setCurrentTheme(currentTheme, currentWallpaperComponentId); - for (String key : currentTheme.keySet()) { - mSelectedComponentsMap.put(key, currentTheme.get(key)); - } - mSelectedWallpaperComponentId = currentWallpaperComponentId.value; - } - - @Override - public boolean componentsChanged() { - // If an external wallpaper/ls are set then something changed! - if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; - - for (String key : mSelectedComponentsMap.keySet()) { - String current = mCurrentTheme.get(key); - if (current == null || !current.equals(mSelectedComponentsMap.get(key))) { - return true; - } - if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && - mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { - return true; - } - } - return false; - } - - @Override - protected void applyThemeWhenPopulated(String pkgName, List components) { - super.applyThemeWhenPopulated(pkgName, components); - populateComponentsToApply(pkgName, components); - } - - private void populateComponentsToApply(String pkgName, List components) { - String selection = ThemesColumns.PKG_NAME + "=?"; - String[] selectionArgs = { pkgName }; - Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, - null, selection, selectionArgs, null); - if (c != null) { - if (c.getCount() > 0 && c.moveToFirst()) { - mSelectedComponentsMap.clear(); - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ALARMS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ALARMS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_BOOT_ANIM)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_BOOT_ANIM, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_FONTS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_FONTS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ICONS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ICONS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LAUNCHER)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LOCKSCREEN)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NAVIGATION_BAR)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NOTIFICATIONS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NOTIFICATIONS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_OVERLAYS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_OVERLAYS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_RINGTONES)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_RINGTONES, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_STATUS_BAR)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_STATUS_BAR, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, pkgName); - } - } - c.close(); - } - - // strip out any components that are not in the components list - if (components != null) { - Iterator> iterator = - mSelectedComponentsMap.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (!components.contains(entry.getKey())) { - iterator.remove(); - } - } - } - } - - private void loadComponentsToApply() { - for (String component : mSelectedComponentsMap.keySet()) { - loadComponentFromPackage(mSelectedComponentsMap.get(component), component, - mSelectedWallpaperComponentId); - } - } - - private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // only update if we are the current visible fragment or if there is no theme - // being applied. - ThemeManager tm = getThemeManager(); - if (!tm.isThemeApplying() || getUserVisibleHint()) { - final WallpaperManager wm = WallpaperManager.getInstance(context); - if (wm.getWallpaperInfo() != null) { - addSurfaceView(mSurfaceView); - } else { - removeSurfaceView(mSurfaceView); - } - - Drawable wp = context == null ? null : wm.getDrawable(); - if (wp != null) { - mWallpaper.setImageDrawable(wp); - mWallpaperCard.setWallpaper(wp); - } - } - } - }; - - private void setCustomized(boolean customized) { - mReset.setVisibility(customized ? View.VISIBLE : View.GONE); - mThemeTagLayout.setCustomizedTagEnabled(customized); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_ALL: - if (args != null) { - String pkgName = args.getString(ARG_PACKAGE_NAME); - if (pkgName != null) { - return super.onCreateLoader(id, args); - } - } - return CursorLoaderHelper.myThemeFragmentCursorLoader(getActivity(), id); - default: - // Only LOADER_ID_ALL differs for MyThemeFragment - return super.onCreateLoader(id, args); - } - } - - @Override - public void onLoadFinished(Loader loader, Cursor c) { - super.onLoadFinished(loader, c); - // if the theme is resetting, we need to apply these changes now that the supported - // theme components have been properly set. - if (loader.getId() == LOADER_ID_ALL) { - if (mThemeResetting) { - applyTheme(); - } else if (mApplyThemeOnPopulated) { - loadComponentsToApply(); - applyTheme(); - } else if (mSelectedComponentsMap.size() == 0) { - //Re-populates selected components with current theme. Why? - //We got here because the cursor was reloaded after the user pressed back and no - //changes were applied, causing the selected components map to be wiped out - mSelectedComponentsMap.putAll(mCurrentTheme); - } - } - } - - @Override - protected Map fillMissingComponentsWithDefault( - Map originalMap) { - // Only the ThemeFragment should be altering this, for the MyThemeFragment this is not - // desirable as it changes components the user did not even touch. - return originalMap; - } - - @Override - protected ThemeChangeRequest getThemeChangeRequestForComponents( - Map componentMap) { - return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST_MIXNMATCH); - } - - @Override - protected Map getComponentsToApply() { - Map componentsToApply = mThemeResetting - ? getEmptyComponentsMap() - : new HashMap(); - if (mThemeResetting) { - final String pkgName = getThemePackageName(); - for (String component : mBaseThemeSupportedComponents) { - componentsToApply.put(component, pkgName); - } - } else { - // Only apply components that actually changed - for (String component : mSelectedComponentsMap.keySet()) { - String currentPkg = mCurrentTheme.get(component); - String selectedPkg = mSelectedComponentsMap.get(component); - if (currentPkg == null || mThemeResetting || !currentPkg.equals(selectedPkg) || - mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { - componentsToApply.put(component, selectedPkg); - } - } - if (mExternalLockscreenUri != null) { - if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { - componentsToApply.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); - } - if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { - componentsToApply.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); - } - } - } - return componentsToApply; - } - - @Override - protected void populateSupportedComponents(Cursor c) { - } - - @Override - protected Boolean shouldShowComponentCard(String component) { - return true; - } - - @Override - protected void loadTitle(Cursor c) { - mTitle.setText(mBaseThemeName); - mAuthor.setText(mBaseThemeAuthor); - } - - @Override - protected void loadWallpaper(Cursor c, boolean animate) { - mExternalWallpaperUri = null; - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadWallpaper(c, animate); - return; - } - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mWallpaperCard, true); - } - - int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); - final Resources res = getResources(); - final Context context = getActivity(); - final WallpaperManager wm = WallpaperManager.getInstance(context); - if (wm.getWallpaperInfo() != null) { - addSurfaceView(mSurfaceView); - } else { - removeSurfaceView(mSurfaceView); - } - - Drawable wp = context == null ? null : wm.getDrawable(); - if (wp == null) { - Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); - if (bmp != null) wp = new BitmapDrawable(res, bmp); - } - if (wp != null) { - mWallpaper.setImageDrawable(wp); - mWallpaperCard.setWallpaper(wp); - setCardTitle(mWallpaperCard, mCurrentTheme.get(ThemesColumns.MODIFIES_LAUNCHER), - getString(R.string.wallpaper_label)); - } else { - mWallpaperCard.clearWallpaper(); - mWallpaperCard.setEmptyViewEnabled(true); - setAddComponentTitle(mWallpaperCard, getString(R.string.wallpaper_label)); - } - - if (animate) { - animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); - } - } - - @Override - protected void loadLockScreen(Cursor c, boolean animate) { - mExternalLockscreenUri = null; - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadLockScreen(c, animate); - return; - } - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mLockScreenCard, true); - } - - //If the current theme includes a lock wallpaper, the WallpaperMgr will - //return a valid Drawable we can display in the card. However, if the user - //picked a LLS, we need to get the path from the provider and manually load the bitmap - int wpIdx = c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW); - Drawable wp = null; - if (wpIdx >= 0) { - final Resources res = getResources(); - Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); - if (bmp != null) wp = new BitmapDrawable(res, bmp); - } else { - final Context context = getActivity(); - wp = context == null ? null : - WallpaperManager.getInstance(context).getFastKeyguardDrawable(); - } - if (wp != null) { - mLockScreenCard.setWallpaper(wp); - } else if (!mSelectedComponentsMap.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { - mLockScreenCard.clearWallpaper(); - mLockScreenCard.setEmptyViewEnabled(true); - setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); - } - - if (animate) { - animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); - } - } - - @Override - protected void loadFont(Cursor c, boolean animate) { - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadFont(c, animate); - return; - } - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mFontPreview, true); - } - setCardTitle(mFontCard, mCurrentTheme.get(ThemesColumns.MODIFIES_FONTS), - getString(R.string.font_label)); - - TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); - ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), - getAppliedFontPackageName()); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - mFontPreview.setTypeface(mTypefaceNormal); - if (animate) { - animateContentChange(R.id.font_preview_container, mFontPreview, overlay); - } - } - - @Override - protected void loadAudible(int type, Cursor c, boolean animate) { - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadAudible(type, c, animate); - return; - } - ComponentCardView audibleContainer = null; - ImageView playPause = null; - String modsComponent = ""; - switch (type) { - case RingtoneManager.TYPE_RINGTONE: - audibleContainer = mRingtoneCard; - playPause = mRingtonePlayPause; - modsComponent = ThemesColumns.MODIFIES_RINGTONES; - break; - case RingtoneManager.TYPE_NOTIFICATION: - audibleContainer = mNotificationCard; - playPause = mNotificationPlayPause; - modsComponent = ThemesColumns.MODIFIES_NOTIFICATIONS; - break; - case RingtoneManager.TYPE_ALARM: - audibleContainer = mAlarmCard; - playPause = mAlarmPlayPause; - modsComponent = ThemesColumns.MODIFIES_ALARMS; - break; - } - if (audibleContainer == null) return; - - if (playPause == null) { - playPause = - (ImageView) audibleContainer.findViewById(R.id.play_pause); - } - TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); - MediaPlayer mp = mMediaPlayers.get(playPause); - if (mp == null) { - mp = new MediaPlayer(); - } - - final Context context = getActivity(); - Ringtone ringtone = null; - try { - Uri ringtoneUri = AudioUtils.loadDefaultAudible(context, type, mp); - if (ringtoneUri != null) ringtone = RingtoneManager.getRingtone(context, ringtoneUri); - } catch (IOException e) { - Log.w(TAG, "Unable to load default sound ", e); - } - - if (ringtone != null) { - title.setText(ringtone.getTitle(context)); - setCardTitle(audibleContainer, mCurrentTheme.get(modsComponent), - getAudibleLabel(type)); - } else { - title.setText(getString(R.string.audible_title_none)); - setAddComponentTitle(audibleContainer, getAudibleLabel(type)); - playPause.setVisibility(View.INVISIBLE); - audibleContainer.setEmptyViewEnabled(true); - } - - playPause.setTag(mp); - mMediaPlayers.put(playPause, mp); - playPause.setOnClickListener(mPlayPauseClickListener); - mp.setOnCompletionListener(mPlayCompletionListener); - } - - @Override - protected void loadStatusBar(Cursor c, boolean animate) { - super.loadStatusBar(c, animate); - setCardTitle(mStatusBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_STATUS_BAR), - getString(R.string.statusbar_label)); - } - - @Override - protected void loadIcons(Cursor c, boolean animate) { - super.loadIcons(c, animate); - setCardTitle(mIconCard, mCurrentTheme.get(ThemesColumns.MODIFIES_ICONS), - getString(R.string.icon_label)); - } - - @Override - protected void loadNavBar(Cursor c, boolean animate) { - super.loadNavBar(c, animate); - setCardTitle(mNavBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_NAVIGATION_BAR), - getString(R.string.navbar_label)); - } - - @Override - protected void loadStyle(Cursor c, boolean animate) { - super.loadStyle(c, animate); - setCardTitle(mStyleCard, mCurrentTheme.get(ThemesColumns.MODIFIES_OVERLAYS), - getString(R.string.style_label)); - } - - @Override - protected void loadBootAnimation(Cursor c) { - super.loadBootAnimation(c); - setCardTitle(mBootAnimationCard, mCurrentTheme.get(ThemesColumns.MODIFIES_BOOT_ANIM), - getString(R.string.boot_animation_label)); - } - - @Override - public String getThemePackageName() { - if (mBaseThemePkgName == null) { - // check if the package name is defined in the arguments bundle - Bundle bundle = getArguments(); - if (bundle != null) { - mBaseThemePkgName = bundle.getString(ARG_BASE_THEME_PACKAGE_NAME); - } - } - return mBaseThemePkgName; - } - - private SurfaceView createSurfaceView() { - final Context context = getActivity(); - if (context == null) return null; - - SurfaceView sv = new SurfaceView(context); - final Resources res = context.getResources(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - res.getDimensionPixelSize(R.dimen.wallpaper_preview_width), - res.getDimensionPixelSize(R.dimen.theme_preview_height), - Gravity.CENTER_HORIZONTAL); - sv.setLayoutParams(params); - - return sv; - } - - private void addSurfaceView(SurfaceView sv) { - if (mShadowFrame.indexOfChild(mSurfaceView) < 0) { - int idx = mShadowFrame.indexOfChild(mWallpaper); - mShadowFrame.addView(sv, idx + 1); - } - } - - private void removeSurfaceView(SurfaceView sv) { - if (mShadowFrame.indexOfChild(mSurfaceView) >= 0) { - mShadowFrame.removeView(sv); - } - } - - /** - * Populates mBaseThemeSupportedComponents. - * @param pkgName Package name of the base theme used - */ - private void populateBaseThemeSupportedComponents(String pkgName) { - String selection = ThemesColumns.PKG_NAME + "=?"; - String[] selectionArgs = { pkgName }; - Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, - null, selection, selectionArgs, null); - if (c != null) { - if (c.moveToFirst()) { - List components = ThemeUtils.getAllComponents(); - final String baseThemePackageName = getThemePackageName(); - for (String component : components) { - int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int modifiesCompIdx = c.getColumnIndex(component); - - String pkg = pkgIdx >= 0 ? c.getString(pkgIdx) : null; - boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); - if (supported && baseThemePackageName.equals(pkg)) { - mBaseThemeSupportedComponents.add(component); - } - } - } - c.close(); - } - } -} diff --git a/src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java b/src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java deleted file mode 100644 index b60a631..0000000 --- a/src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.cyngn.theme.chooser; - -import android.os.Bundle; -import android.os.Parcelable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.PagerAdapter; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Implementation of {@link android.support.v4.view.PagerAdapter} that - * uses a {@link android.support.v4.app.Fragment} to manage each page. This class also handles - * saving and restoring of fragment's state. - * - *

This version of the pager is more useful when there are a large number - * of pages, working more like a list view. When pages are not visible to - * the user, their entire fragment may be destroyed, only keeping the saved - * state of that fragment. This allows the pager to hold on to much less - * memory associated with each visited page as compared to - * {@link android.support.v4.app.FragmentPagerAdapter} at the cost of potentially more overhead when - * switching between pages. - * - *

When using FragmentPagerAdapter the host ViewPager must have a - * valid ID set.

- * - *

Subclasses only need to implement {@link #getItem(int)} - * and {@link #getCount()} to have a working adapter. - * - *

Here is an example implementation of a pager containing fragments of - * lists: - * - * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java - * complete} - * - *

The R.layout.fragment_pager resource of the top-level fragment is: - * - * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml - * complete} - * - *

The R.layout.fragment_pager_list resource containing each - * individual fragment's layout is: - * - * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml - * complete} - */ -public abstract class NewFragmentStatePagerAdapter extends PagerAdapter { - private static final String TAG = NewFragmentStatePagerAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; - - private final FragmentManager mFragmentManager; - private FragmentTransaction mCurTransaction = null; - - private long[] mItemIds = new long[] {}; - private ArrayList mSavedState = new ArrayList(); - private ArrayList mFragments = new ArrayList(); - private Fragment mCurrentPrimaryItem = null; - - public NewFragmentStatePagerAdapter(FragmentManager fm) { - - mFragmentManager = fm; - createIdCache(); - } - - /** - * Return the Fragment associated with a specified position. - */ - public abstract Fragment getItem(int position); - - /** - * Return a unique identifier for the item at the given position. - */ - public abstract long getItemId(int position); - - @Override - public void startUpdate(ViewGroup container) { - } - - private void checkForIdChanges() { - long[] newItemIds = new long[getCount()]; - for (int i = 0; i < newItemIds.length; i++) { - newItemIds[i] = getItemId(i); - } - - if (!Arrays.equals(mItemIds, newItemIds)) { - ArrayList newSavedState = new ArrayList(); - ArrayList newFragments = new ArrayList(); - - for (int oldPosition = 0; oldPosition < mItemIds.length; oldPosition++) { - int newPosition = POSITION_NONE; - for (int i = 0; i < newItemIds.length; i++) { - if (mItemIds[oldPosition] == newItemIds[i]) { - newPosition = i; - break; - } - } - if (newPosition >= 0) { - if (oldPosition < mSavedState.size()) { - Fragment.SavedState savedState = mSavedState.get(oldPosition); - if (savedState != null) { - while (newSavedState.size() <= newPosition) { - newSavedState.add(null); - } - newSavedState.set(newPosition, savedState); - } - } - if (oldPosition < mFragments.size()) { - Fragment fragment = mFragments.get(oldPosition); - if (fragment != null) { - while (newFragments.size() <= newPosition) { - newFragments.add(null); - } - newFragments.set(newPosition, fragment); - } - } - } - } - - mItemIds = newItemIds; - mSavedState = newSavedState; - mFragments = newFragments; - } - } - - @Override - public void notifyDataSetChanged() { - checkForIdChanges(); - - super.notifyDataSetChanged(); - } - - /** - * Create the initial set of item IDs. Run this after you have set your adapter data. - */ - public void createIdCache() { - // If we have already stored ids, don't overwrite them - if (mItemIds.length == 0) { - // getCount might have overhead, so run it as late as possible - final int count = getCount(); - if (count > 0) { - mItemIds = new long[count]; - for (int i = 0; i < count; i++) { - mItemIds[i] = getItemId(i); - } - } - } - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - - createIdCache(); - - // If we already have this item instantiated, there is nothing - // to do. This can happen when we are restoring the entire pager - // from its saved state, where the fragment manager has already - // taken care of restoring the fragments we previously had instantiated. - if (mFragments.size() > position) { - Fragment f = mFragments.get(position); - if (f != null) { - return f; - } - } - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - - Fragment fragment = getItem(position); - if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); - if (mSavedState.size() > position) { - Fragment.SavedState fss = mSavedState.get(position); - if (fss != null) { - fragment.setInitialSavedState(fss); - } - } - while (mFragments.size() <= position) { - mFragments.add(null); - } - fragment.setMenuVisibility(false); - fragment.setUserVisibleHint(false); - mFragments.set(position, fragment); - mCurTransaction.add(container.getId(), fragment); - - return fragment; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment)object; - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object - + " v=" + ((Fragment)object).getView()); - while (mSavedState.size() <= position) { - mSavedState.add(null); - } - mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); - - // Only set the position to null if the fragment being removed is at "position" - // We do this because checkForIdChanges updates the mFragments list and if a fragment - // was removed then the fragment at "position" is not the fragment that was removed. - if (position < mFragments.size() && fragment == mFragments.get(position)) { - mFragments.set(position, null); - } - mCurTransaction.remove(fragment); - } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment)object; - if (fragment != mCurrentPrimaryItem) { - if (mCurrentPrimaryItem != null) { - mCurrentPrimaryItem.setMenuVisibility(false); - mCurrentPrimaryItem.setUserVisibleHint(false); - } - if (fragment != null) { - fragment.setMenuVisibility(true); - fragment.setUserVisibleHint(true); - } - mCurrentPrimaryItem = fragment; - } - } - - @Override - public void finishUpdate(ViewGroup container) { - if (mCurTransaction != null) { - mCurTransaction.commitAllowingStateLoss(); - mCurTransaction = null; - mFragmentManager.executePendingTransactions(); - } - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return ((Fragment)object).getView() == view; - } - - @Override - public Parcelable saveState() { - Bundle state = null; - - mItemIds = new long[getCount()]; - for (int i = 0; i < mItemIds.length; i++) { - mItemIds[i] = getItemId(i); - } - if (mSavedState.size() > 0) { - state = new Bundle(); - - if (mItemIds.length > 0) { - state.putLongArray("itemids", mItemIds); - } - - Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(fss); - state.putParcelableArray("states", fss); - } - for (int i=0; i keys = bundle.keySet(); - for (String key: keys) { - if (key.startsWith("f")) { - int index = Integer.parseInt(key.substring(1)); - Fragment f = mFragmentManager.getFragment(bundle, key); - if (f != null) { - while (mFragments.size() <= index) { - mFragments.add(null); - } - f.setMenuVisibility(false); - mFragments.set(index, f); - } else { - Log.w(TAG, "Bad fragment at key " + key); - } - } - } - checkForIdChanges(); - } - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/NotificationHijackingService.java b/src/com/cyngn/theme/chooser/NotificationHijackingService.java deleted file mode 100644 index ca53791..0000000 --- a/src/com/cyngn/theme/chooser/NotificationHijackingService.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.chooser; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.provider.Settings; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; -import android.text.TextUtils; - -public class NotificationHijackingService extends NotificationListenerService { - private static final String TAG = NotificationHijackingService.class.getName(); - private static final String GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"; - private static final String ACTION_INSTALLED = - "com.android.vending.SUCCESSFULLY_INSTALLED_CLICKED"; - private static final String EXTRA_PACKAGE_NAME = "package_name"; - - @Override - public void onNotificationPosted(StatusBarNotification sbn) { - if (GOOGLE_PLAY_PACKAGE_NAME.equals(sbn.getPackageName())) { - PendingIntent contentIntent = sbn.getNotification().contentIntent; - if (contentIntent == null) return; - Intent intent = contentIntent.getIntent(); - if (intent == null) return; - String action = intent.getAction(); - if (ACTION_INSTALLED.equals(action)) { - String pkgName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - try { - PackageInfo pi = getPackageManager().getPackageInfo(pkgName, 0); - if (pi != null) { - if (pi.themeInfo != null) { - cancelNotification(GOOGLE_PLAY_PACKAGE_NAME, sbn.getTag(), sbn.getId()); - } - } - } catch (PackageManager.NameNotFoundException e) { - } - } - } - } - - @Override - public void onNotificationRemoved(StatusBarNotification sbn) { - } - - // ensure that this notification listener is enabled. - // the service watches for google play notifications - public static void ensureEnabled(Context context) { - ComponentName me = new ComponentName(context, NotificationHijackingService.class); - String meFlattened = me.flattenToString(); - - String existingListeners = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - - if (!TextUtils.isEmpty(existingListeners)) { - if (existingListeners.contains(meFlattened)) { - return; - } else { - existingListeners += ":" + meFlattened; - } - } else { - existingListeners = meFlattened; - } - - Settings.Secure.putString(context.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - existingListeners); - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/PagerContainer.java b/src/com/cyngn/theme/chooser/PagerContainer.java deleted file mode 100644 index 1a5e51a..0000000 --- a/src/com/cyngn/theme/chooser/PagerContainer.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2012 Wireless Designs, LLC - * Portions copyright (C) 2014, The Cyanogen, Inc - * - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.cyngn.theme.chooser; - -import android.content.Context; -import android.graphics.Point; -import android.support.v4.view.ThemeViewPager; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -/** - * PagerContainer: A layout that displays a ViewPager with its children that are outside - * the typical pager bounds. - */ -public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener { - private static final int ANIMATE_OUT_DURATION = 300; - private static final int ANIMATE_OUT_INTERPOLATE_FACTOR = 1; - private static final int ANIMATE_IN_DURATION = 300; - private static final int ANIMATE_IN_INTERPOLATE_FACTOR = 2; - - private ThemeViewPager mPager; - private Point mCenter = new Point(); - private Point mInitialTouch = new Point(); - private int mCollapsedHeight; - private boolean mIsAnimating = false; - - boolean mNeedsRedraw = false; - - public PagerContainer(Context context) { - this(context, null, 0); - } - - public PagerContainer(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PagerContainer(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - mCollapsedHeight = generateLayoutParams(attrs).height; - - //Disable clipping of children so non-selected pages are visible - setClipChildren(false); - } - - @Override - protected void onFinishInflate() { - try { - mPager = (ThemeViewPager) getChildAt(0); - mPager.setOnPageChangeListener(this); - } catch (Exception e) { - throw new IllegalStateException("The root child of PagerContainer must be a ViewPager"); - } - } - - public ThemeViewPager getViewPager() { - return mPager; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mCenter.x = w / 2; - mCenter.y = h / 2; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mIsAnimating) return true; - return super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - // Do not allow touch events to propagate if we are animating - if (mIsAnimating) return true; - - //We capture any touches not already handled by the ViewPager - // to implement scrolling from a touch outside the pager bounds. - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mInitialTouch.x = (int)ev.getX(); - mInitialTouch.y = (int)ev.getY(); - default: - ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y); - break; - } - - return mPager.dispatchTouchEvent(ev); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - //Force the container to redraw on scrolling. - //Without this the outer pages render initially and then stay static - if (mNeedsRedraw) invalidate(); - } - - @Override - public void onPageSelected(int position) { } - - @Override - public void onPageScrollStateChanged(int state) { - mNeedsRedraw = (state != ThemeViewPager.SCROLL_STATE_IDLE); - } - - public void setIsAnimating(boolean isAnimating) { - mIsAnimating = isAnimating; - } - - public void expand() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); - params.height = LinearLayout.LayoutParams.MATCH_PARENT; - setLayoutParams(params); - - mPager.setExpanded(true); - - final int current = mPager.getCurrentItem(); - final int prevY = (int) getY(); - - //Since our viewpager's width is changing to fill the screen - //we must start the left/right children of the current page inwards on first draw - final int lChildPrevXf; - final int rChildPrevXf; - - if (current != 0) { - final View lchild = mPager.getViewForPosition(current - 1); - lChildPrevXf = (int) lchild.getX(); - } else { - lChildPrevXf = 0; - } - - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getViewForPosition(current + 1); - rChildPrevXf = (int) rchild.getX(); - } else { - rChildPrevXf = 0; - } - - - final ViewTreeObserver observer = mPager.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - if (current != 0) { - View lchild = mPager.getViewForPosition(current - 1); - lchild.setTranslationY(prevY - getY()); - lchild.setX(lChildPrevXf); - animateChildOut(lchild, -getWidth()); - } - - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getViewForPosition(current + 1); - rchild.setX(rChildPrevXf); - rchild.setTranslationY(prevY - getY()); - animateChildOut(rchild, getWidth()); - } - return false; - } - }); - } - - public void collapse() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); - params.height = mCollapsedHeight; - setLayoutParams(params); - - mPager.setExpanded(false); - int current = mPager.getCurrentItem(); - final int prevY = (int) getY(); - - if (current != 0) { - View lchild = mPager.getViewForPosition(current - 1); - lchild.setTranslationY(0); - animateChildIn(lchild); - } - - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getViewForPosition(current + 1); - rchild.setTranslationY(0); - animateChildIn(rchild); - } - } - - private void animateChildOut(final View v, float endX) { - v.animate() - .translationX(endX) - .setDuration(ANIMATE_OUT_DURATION) - .setInterpolator(new AccelerateInterpolator(ANIMATE_OUT_INTERPOLATE_FACTOR)); - } - - private void animateChildIn(final View v) { - v.animate() - .translationX(0) - .setDuration(ANIMATE_IN_DURATION) - .setInterpolator(new DecelerateInterpolator(ANIMATE_IN_INTERPOLATE_FACTOR)); - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/ThemeFragment.java b/src/com/cyngn/theme/chooser/ThemeFragment.java deleted file mode 100644 index 12082e5..0000000 --- a/src/com/cyngn/theme/chooser/ThemeFragment.java +++ /dev/null @@ -1,3045 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.IntEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.WallpaperManager; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.FileUtils; -import android.os.Handler; -import android.support.v4.app.Fragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.text.TextUtils; -import android.util.Log; -import android.util.MutableLong; -import android.util.SparseArray; -import android.view.Display; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.ScaleAnimation; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.PopupMenu; -import android.widget.ProgressBar; -import android.widget.Space; -import android.widget.TextView; -import android.widget.Toast; - -import com.cyngn.theme.chooser.ComponentSelector.OnItemClickedListener; -import com.cyngn.theme.util.AudioUtils; -import com.cyngn.theme.util.BootAnimationHelper; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.IconPreviewHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.ThemedTypefaceHelper; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; -import com.cyngn.theme.util.WallpaperUtils; -import com.cyngn.theme.widget.BootAniImageView; -import com.cyngn.theme.widget.ConfirmCancelOverlay; -import com.cyngn.theme.widget.LockableScrollView; -import com.cyngn.theme.widget.ThemeTagLayout; - -import cyanogenmod.app.ThemeVersion; -import cyanogenmod.providers.CMSettings; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeChangeRequest; -import cyanogenmod.themes.ThemeChangeRequest.RequestType; -import cyanogenmod.themes.ThemeManager; - -import org.cyanogenmod.internal.util.CmLockPatternUtils; -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidParameterException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipFile; - -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; - -import static com.cyngn.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_INVALID; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALL; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_FONT; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_LIVE_LOCK_SCREEN; - -import static cyanogenmod.providers.CMSettings.Secure.LIVE_LOCK_SCREEN_ENABLED; - -import static org.cyanogenmod.internal.util.ThemeUtils.SYSTEM_TARGET_API; - -public class ThemeFragment extends Fragment implements LoaderManager.LoaderCallbacks, - ThemeManager.ThemeChangeListener, ThemeManager.ThemeProcessingListener { - private static final String TAG = ThemeFragment.class.getSimpleName(); - - public static final int ANIMATE_START_DELAY = 200; - public static final int ANIMATE_DURATION = 300; - public static final int ANIMATE_INTERPOLATE_FACTOR = 3; - public static final int ANIMATE_COMPONENT_CHANGE_DURATION = 200; - public static final int ANIMATE_COMPONENT_ICON_DELAY = 50; - public static final int ANIMATE_PROGRESS_IN_DURATION = 500; - public static final int ANIMATE_TITLE_OUT_DURATION = 400; - public static final int ANIMATE_PROGRESS_OUT_DURATION = 400; - public static final int ANIMATE_TITLE_IN_DURATION = 500; - public static final int ANIMATE_APPLY_LAYOUT_DURATION = 300; - - public static final String CURRENTLY_APPLIED_THEME = "currently_applied_theme"; - - private static final ComponentName COMPONENT_DIALER = - new ComponentName("com.android.dialer", "com.android.dialer.DialtactsActivity"); - private static final ComponentName COMPONENT_DIALERNEXT = - new ComponentName("com.cyngn.dialer", "com.android.dialer.DialtactsActivity"); - private static final ComponentName COMPONENT_MESSAGING = - new ComponentName("com.android.mms", "com.android.mms.ui.ConversationList"); - private static final ComponentName COMPONENT_CAMERANEXT = - new ComponentName("com.cyngn.cameranext", "com.android.camera.CameraLauncher"); - private static final ComponentName COMPONENT_CAMERA = - new ComponentName("com.android.camera2", "com.android.camera.CameraActivity"); - private static final ComponentName COMPONENT_BROWSER = - new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"); - private static final ComponentName COMPONENT_SETTINGS = - new ComponentName("com.android.settings", "com.android.settings.Settings"); - private static final ComponentName COMPONENT_CALENDAR = - new ComponentName("com.android.calendar", "com.android.calendar.AllInOneActivity"); - private static final ComponentName COMPONENT_GALERY = - new ComponentName("com.android.gallery3d", "com.android.gallery3d.app.GalleryActivity"); - - private static final String CAMERA_NEXT_PACKAGE = "com.cyngn.cameranext"; - private static final String DIALER_NEXT_PACKAGE = "com.cyngn.dialer"; - - private static final int ADDITIONAL_CONTENT_SPACE_ID = 123456; - private static final long SLIDE_CONTENT_ANIM_DURATION = 300L; - private static final long LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION = 400; - private static final long SHOW_LOCK_SCREEN_CARD_DELAY = 500; - - private static final int DEFAULT_WIFI_MARGIN = 0; - private static final int DEFAULT_CLOCK_COLOR = Color.WHITE; - - protected static final String WALLPAPER_NONE = ""; - protected static final String LOCKSCREEN_NONE = ""; - - protected static final String ARG_PACKAGE_NAME = "pkgName"; - protected static final String ARG_COMPONENT_ID = "cmpntId"; - protected static final String ARG_SKIP_LOADING_ANIM = "skipLoadingAnim"; - protected static final String ARG_ANIMATE_TO_LOCK_SCREEN_CARD = "animateToLockScreenCard"; - - private static final String LLS_PACKAGE_NAME = "com.cyngn.lockscreen.live"; - private static final String LLS_PROVIDER_NAME = - "com.cyngn.lockscreen.live.LockScreenProviderService"; - - private static final int PERMISSION_REQUEST = 100; - - protected static ComponentName[] sIconComponents; - - protected static TypefaceHelperCache sTypefaceHelperCache; - - /** - * Maps the card's resource ID to a theme component - */ - private final SparseArray mCardIdsToComponentTypes = new SparseArray(); - - protected String mPkgName; - protected Typeface mTypefaceNormal; - protected int mBatteryStyle; - - protected LockableScrollView mScrollView; - protected ViewGroup mScrollContent; - protected ViewGroup mPreviewContent; // Contains icons, font, nav/status etc. Not wallpaper - protected View mLoadingView; - - //Status Bar Views - protected ComponentCardView mStatusBarCard; - protected ImageView mBluetooth; - protected ImageView mWifi; - protected ImageView mSignal; - protected ImageView mBattery; - protected TextView mClock; - - // Other Misc Preview Views - protected FrameLayout mShadowFrame; - protected ImageView mWallpaper; - protected ViewGroup mStatusBar; - protected TextView mFontPreview; - protected ComponentCardView mStyleCard; - protected ComponentCardView mFontCard; - protected ComponentCardView mIconCard; - protected ComponentCardView mBootAnimationCard; - protected BootAniImageView mBootAnimation; - - // Nav Bar Views - protected ComponentCardView mNavBarCard; - protected ViewGroup mNavBar; - protected ImageView mBackButton; - protected ImageView mHomeButton; - protected ImageView mRecentButton; - - // Title Card Views - protected ViewGroup mTitleCard; - protected ViewGroup mTitleLayout; - protected TextView mTitle; - protected TextView mAuthor; - protected ImageView mCustomize; - protected ImageView mOverflow; - protected ImageView mDelete; - protected ImageView mReset; - protected ProgressBar mProgress; - - // Additional Card Views - protected LinearLayout mAdditionalCards; - protected WallpaperCardView mWallpaperCard; - protected WallpaperCardView mLockScreenCard; - - // Style views - protected ImageView mStylePreview; - - // Sound cards - protected ComponentCardView mRingtoneCard; - protected ImageView mRingtonePlayPause; - protected ComponentCardView mNotificationCard; - protected ImageView mNotificationPlayPause; - protected ComponentCardView mAlarmCard; - protected ImageView mAlarmPlayPause; - protected Map mMediaPlayers; - - protected Handler mHandler; - - protected int mActiveCardId = -1; - protected ComponentSelector mSelector; - // Supported components for the theme this fragment represents - protected Map mSelectedComponentsMap = new HashMap(); - protected Long mSelectedWallpaperComponentId; - // Current system theme configuration as component -> pkgName - protected Map mCurrentTheme = new HashMap(); - protected MutableLong mCurrentWallpaperComponentId = new MutableLong(DEFAULT_COMPONENT_ID); - // Set of components available in the base theme - protected HashSet mBaseThemeSupportedComponents = new HashSet(); - protected Cursor mCurrentCursor; - protected int mCurrentLoaderId; - protected boolean mThemeResetting; - protected boolean mSkipLoadingAnim; - - // Accept/Cancel overlay - protected ConfirmCancelOverlay mConfirmCancelOverlay; - - // Customize/Reset theme layout - protected View mCustomizeResetLayout; - protected View mResetButton; - protected View mCustomizeButton; - protected View mDismissButton; - - // Processing theme layout - protected View mProcessingThemeLayout; - - protected ThemeTagLayout mThemeTagLayout; - - protected View mClickableView; - protected String mBaseThemePkgName; - - protected Uri mExternalWallpaperUri; - protected Uri mExternalLockscreenUri; - - protected boolean mExpanded; - protected boolean mProcessingResources; - protected boolean mApplyThemeOnPopulated; - - protected boolean mIsLegacyTheme; - - private Runnable mAfterPermissionGrantedRunnable; - - private static final int mThemeVersion = ThemeVersion.getVersion(); - - protected boolean mShowLockScreenSelectorAfterContentLoaded; - - protected enum CustomizeResetAction { - Customize, - Reset, - Dismiss - } - - static ThemeFragment newInstance(String pkgName, boolean skipLoadingAnim) { - ThemeFragment f = new ThemeFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, pkgName); - args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); - args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); - f.setArguments(args); - return f; - } - - /** - * When creating, retrieve this instance's number from its arguments. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Context context = getActivity(); - mPkgName = getArguments().getString(ARG_PACKAGE_NAME); - mSkipLoadingAnim = getArguments().getBoolean(ARG_SKIP_LOADING_ANIM); - // TODO: Load from settings once available - mBatteryStyle = 0;/*Settings.System.getInt(context.getContentResolver(), - Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ - - getIconComponents(context); - if (sTypefaceHelperCache == null) { - sTypefaceHelperCache = TypefaceHelperCache.getInstance(); - } - ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, mPkgName); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - - mHandler = new Handler(); - - mCardIdsToComponentTypes.put(R.id.status_bar_container, MODIFIES_STATUS_BAR); - mCardIdsToComponentTypes.put(R.id.font_preview_container, MODIFIES_FONTS); - mCardIdsToComponentTypes.put(R.id.icon_container, MODIFIES_ICONS); - mCardIdsToComponentTypes.put(R.id.navigation_bar_container, MODIFIES_NAVIGATION_BAR); - mCardIdsToComponentTypes.put(R.id.wallpaper_card, MODIFIES_LAUNCHER); - mCardIdsToComponentTypes.put(R.id.lockscreen_card, MODIFIES_LOCKSCREEN); - mCardIdsToComponentTypes.put(R.id.style_card, MODIFIES_OVERLAYS); - mCardIdsToComponentTypes.put(R.id.bootani_preview_container, MODIFIES_BOOT_ANIM); - mCardIdsToComponentTypes.put(R.id.ringtone_preview_container, MODIFIES_RINGTONES); - mCardIdsToComponentTypes.put(R.id.notification_preview_container, MODIFIES_NOTIFICATIONS); - mCardIdsToComponentTypes.put(R.id.alarm_preview_container, MODIFIES_ALARMS); - - mMediaPlayers = new HashMap(3); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.fragment_pager_list, container, false); - - mScrollView = (LockableScrollView) v.findViewById(android.R.id.list); - mScrollView.setScrollingEnabled(false); - mScrollContent = (ViewGroup) mScrollView.getChildAt(0); - mPreviewContent = (ViewGroup) v.findViewById(R.id.preview_container); - mLoadingView = v.findViewById(R.id.loading_view); - mThemeTagLayout = (ThemeTagLayout) v.findViewById(R.id.tag_layout); - - // Status Bar - mStatusBarCard = (ComponentCardView) v.findViewById(R.id.status_bar_container); - mStatusBar = (ViewGroup) v.findViewById(R.id.status_bar); - mBluetooth = (ImageView) v.findViewById(R.id.bluetooth_icon); - mWifi = (ImageView) v.findViewById(R.id.wifi_icon); - mSignal = (ImageView) v.findViewById(R.id.signal_icon); - mBattery = (ImageView) v.findViewById(R.id.battery); - mClock = (TextView) v.findViewById(R.id.clock); - - // Wallpaper / Font / Icons / etc - mWallpaper = (ImageView) v.findViewById(R.id.wallpaper); - mFontCard = (ComponentCardView) v.findViewById(R.id.font_preview_container); - mFontPreview = (TextView) v.findViewById(R.id.font_preview); - mFontPreview.setTypeface(mTypefaceNormal); - mIconCard = (ComponentCardView) v.findViewById(R.id.icon_container); - mShadowFrame = (FrameLayout) v.findViewById(R.id.shadow_frame); - mStyleCard = (ComponentCardView) v.findViewById(R.id.style_card); - mStylePreview = (ImageView) v.findViewById(R.id.style_preview); - mBootAnimationCard = (ComponentCardView) v.findViewById(R.id.bootani_preview_container); - mBootAnimation = - (BootAniImageView) mBootAnimationCard.findViewById(R.id.bootani_preview); - mRingtoneCard = (ComponentCardView) v.findViewById(R.id.ringtone_preview_container); - mRingtonePlayPause = (ImageView) mRingtoneCard.findViewById(R.id.play_pause); - mNotificationCard = (ComponentCardView) v.findViewById(R.id.notification_preview_container); - mNotificationPlayPause = (ImageView) mNotificationCard.findViewById(R.id.play_pause); - mAlarmCard = (ComponentCardView) v.findViewById(R.id.alarm_preview_container); - mAlarmPlayPause = (ImageView) mAlarmCard.findViewById(R.id.play_pause); - - // Nav Bar - mNavBarCard = (ComponentCardView) v.findViewById(R.id.navigation_bar_container); - mNavBar = (ViewGroup) v.findViewById(R.id.navigation_bar); - mBackButton = (ImageView) v.findViewById(R.id.back_button); - mHomeButton = (ImageView) v.findViewById(R.id.home_button); - mRecentButton = (ImageView) v.findViewById(R.id.recent_button); - - // Title Card - mTitleCard = (ViewGroup)v.findViewById(R.id.title_card); - mTitleLayout = (ViewGroup) v.findViewById(R.id.title_layout); - mTitle = (TextView) v.findViewById(R.id.title); - mAuthor = (TextView) v.findViewById(R.id.author); - mProgress = (ProgressBar) v.findViewById(R.id.apply_progress); - mOverflow = (ImageView) v.findViewById(R.id.overflow); - mOverflow.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (isShowingConfirmCancelOverlay()) { - hideConfirmCancelOverlay(); - } else if (isShowingCustomizeResetLayout()) { - hideCustomizeResetLayout(); - } - - PopupMenu popupmenu = new PopupMenu(getActivity(), mTitleCard, Gravity.END); - popupmenu.getMenuInflater().inflate(R.menu.overflow, popupmenu.getMenu()); - - Menu menu = popupmenu.getMenu(); - if (CURRENTLY_APPLIED_THEME.equals(mPkgName) || - mPkgName.equals(Utils.getDefaultThemePackageName(getActivity())) || - mPkgName.equals(ThemeConfig.SYSTEM_DEFAULT)) { - menu.findItem(R.id.menu_delete).setEnabled(false); - } - if (!mThemeTagLayout.isCustomizedTagEnabled()) { - menu.findItem(R.id.menu_reset).setVisible(false); - } - - popupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - return onPopupMenuItemClick(item); - } - }); - popupmenu.show(); - } - }); - mCustomize = (ImageView) v.findViewById(R.id.customize); - mCustomize.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - if (!isShowingConfirmCancelOverlay() && !isShowingCustomizeResetLayout()) { - getChooserActivity().expand(); - } - } - }); - - mDelete = (ImageView) v.findViewById(R.id.delete); - mDelete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showDeleteThemeOverlay(); - } - }); - if (Utils.getDefaultThemePackageName(getActivity()).equals(mPkgName) || - ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { - mDelete.setVisibility(View.GONE); - } - - mReset = (ImageView) v.findViewById(R.id.reset); - mReset.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showResetThemeOverlay(); - } - }); - mReset.setVisibility(View.GONE); - - if (!Utils.hasNavigationBar(getActivity())) { - adjustScrollViewPaddingTop(); - mNavBarCard.setVisibility(View.GONE); - } - - // Additional cards which should hang out offscreen until expanded - mAdditionalCards = (LinearLayout) v.findViewById(R.id.additional_cards); - - mWallpaperCard = (WallpaperCardView) v.findViewById(R.id.wallpaper_card); - mLockScreenCard = (WallpaperCardView) v.findViewById(R.id.lockscreen_card); - int translationY = getDistanceToMoveBelowScreen(mAdditionalCards); - mAdditionalCards.setTranslationY(translationY); - - mConfirmCancelOverlay = (ConfirmCancelOverlay) v.findViewById(R.id.confirm_cancel_overlay); - mClickableView = v.findViewById(R.id.clickable_view); - - mCustomizeResetLayout = v.findViewById(R.id.customize_reset_theme_layout); - mDismissButton = mCustomizeResetLayout.findViewById(R.id.btn_dismiss); - mDismissButton.setOnClickListener(mCustomizeResetClickListener); - mResetButton = mCustomizeResetLayout.findViewById(R.id.btn_reset); - mResetButton.setOnClickListener(mCustomizeResetClickListener); - mCustomizeButton = mCustomizeResetLayout.findViewById(R.id.btn_customize); - mCustomizeButton.setOnClickListener(mCustomizeResetClickListener); - - mProcessingThemeLayout = v.findViewById(R.id.processing_theme_layout); - - if (mPkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { - mThemeTagLayout.setDefaultTagEnabled(true); - } - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - - if (mSkipLoadingAnim) { - mLoadingView.setVisibility(View.GONE); - mTitleLayout.setAlpha(1f); - } - - getLoaderManager().initLoader(LOADER_ID_ALL, null, this); - - setupCardClickListeners(v); - - return v; - } - - @Override - public void onPause() { - super.onPause(); - stopMediaPlayers(); - } - - @Override - public void onResume() { - super.onResume(); - ThemeManager tm = getThemeManager(); - if (tm != null) { - if (isThemeProcessing()) { - tm.registerProcessingListener(this); - mProcessingThemeLayout.setVisibility(View.VISIBLE); - mCustomize.setVisibility(View.INVISIBLE); - mCustomize.setAlpha(0f); - if (mDelete.getVisibility() != View.GONE) { - mDelete.setVisibility(View.INVISIBLE); - mDelete.setAlpha(0f); - } - mProcessingResources = true; - } else { - mCustomize.setVisibility(View.VISIBLE); - mCustomize.setAlpha(1f); - if (mDelete.getVisibility() != View.GONE) { - mDelete.setVisibility(View.VISIBLE); - mDelete.setAlpha(1f); - } - mProcessingResources = false; - } - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - freeMediaPlayers(); - ThemeManager tm = getThemeManager(); - if (tm != null) { - tm.removeClient(this); - tm.unregisterProcessingListener(this); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == PERMISSION_REQUEST) { - int N = permissions.length; - for (int i = 0; i < N; i++) { - if (READ_EXTERNAL_STORAGE.equals(permissions[i])) { - if (grantResults[i] == PERMISSION_GRANTED) { - // Run the runnable now that we have been granted permission - if (mAfterPermissionGrantedRunnable != null) { - mAfterPermissionGrantedRunnable.run(); - mAfterPermissionGrantedRunnable = null; - } - } else { - // inform the user that they will be unable to pick an image because - // we were not granted permission to do so - Toast.makeText(getActivity(), - R.string.read_external_permission_denied_message, - Toast.LENGTH_LONG).show(); - } - } - } - } - } - - @Override - public void onProgress(int progress) { - mProgress.setProgress(progress); - } - - private void setLiveLockScreenAsKeyguard(boolean setLLS) { - ComponentName cn = null; - if (setLLS) { - try { - final String[] permissions = Utils.getDangerousPermissionsNotGranted(getActivity(), - LLS_PACKAGE_NAME); - if (permissions.length > 0) { - Intent reqIntent = Utils.buildPermissionGrantRequestIntent(getActivity(), - LLS_PACKAGE_NAME, permissions); - if (reqIntent != null) { - startActivity(reqIntent); - } - } - cn = new ComponentName(LLS_PACKAGE_NAME, LLS_PROVIDER_NAME); - } catch (InvalidParameterException e) { - Log.e(TAG, "Package Manager couldn't find package " + LLS_PACKAGE_NAME, e); - return; - } - } - - CmLockPatternUtils lockPatternUtils = new CmLockPatternUtils(getActivity()); - try { - lockPatternUtils.setThirdPartyKeyguard(cn); - } catch (PackageManager.NameNotFoundException e) { - // we should not be here! - } - } - - @Override - public void onFinish(boolean isSuccess) { - // We post a runnable to mHandler so the client is removed from the same thread - mHandler.post(new Runnable() { - @Override - public void run() { - ThemeManager tm = getThemeManager(); - if (tm != null) tm.removeClient(ThemeFragment.this); - } - }); - if (isSuccess) { - if (mExternalLockscreenUri != null) { - // Handle setting an external wallpaper in a separate thread - // Need to do this AFTER ThemeMgr is done processing our change request. - // The external lock screen that we just applied would be removed when - // the change request is setting/clearing the lock screen - new Thread(mApplyExternalLockscreenRunnable).start(); - } - Map appliedComponents = getComponentsToApply(); - boolean modLLS = appliedComponents.containsKey(MODIFIES_LIVE_LOCK_SCREEN); - if (modLLS) { - String pkgName = appliedComponents.get(MODIFIES_LIVE_LOCK_SCREEN); - if (pkgName.equals(LOCKSCREEN_NONE)) { - setLiveLockScreenAsKeyguard(false); - } else { - setLiveLockScreenAsKeyguard(true); - } - } - mProgress.setProgress(100); - animateProgressOut(); - } - getChooserActivity().themeChangeEnd(isSuccess); - } - - @Override - public void onFinishedProcessing(String pkgName) { - if (pkgName.equals(mPkgName) || pkgName.equals(mBaseThemePkgName)) { - ThemeManager tm = getThemeManager(); - if (tm != null) { - tm.unregisterProcessingListener(this); - } - } - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (mThemeTagLayout == null) return; - - if (!isVisibleToUser) { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - } else { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { - PreferenceUtils.removeUpdatedTheme(getActivity(), mPkgName); - } - } - } - - public void setWallpaperImageUri(Uri uri) { - mExternalWallpaperUri = uri; - final Point size = new Point(mWallpaper.getWidth(), mWallpaper.getHeight()); - final Drawable wp = getWallpaperDrawableFromUri(uri, size); - mWallpaperCard.setWallpaper(wp); - mWallpaper.setImageDrawable(wp); - // remove the entry from mSelectedComponentsMap - mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LAUNCHER); - } - - public void setLockscreenImageUri(Uri uri) { - mExternalLockscreenUri = uri; - final Point size = new Point(mLockScreenCard.getWidth(), mLockScreenCard.getHeight()); - final Drawable wp = getWallpaperDrawableFromUri(uri, size); - if (mLockScreenCard.isShowingEmptyView()) { - mLockScreenCard.setEmptyViewEnabled(false); - } - mLockScreenCard.setWallpaper(wp); - // remove the entry from mSelectedComponentsMap - mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); - mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LOCKSCREEN); - } - - protected Drawable getWallpaperDrawableFromUri(Uri uri, Point size) { - final Context context = getActivity(); - final Resources res = context.getResources(); - Bitmap bmp = WallpaperUtils.createPreview(size, context, uri, null, res, 0, 0, false); - if (bmp != null) { - return new BitmapDrawable(res, bmp); - } - return null; - } - - protected ChooserActivity getChooserActivity() { - return (ChooserActivity) getActivity(); - } - - private void adjustScrollViewPaddingTop() { - Resources res = getResources(); - int extraPadding = - res.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; - mScrollView.setPadding(mScrollView.getPaddingLeft(), - mScrollView.getPaddingTop() + extraPadding, mScrollView.getPaddingRight(), - mScrollView.getPaddingBottom()); - } - - protected boolean isThemeProcessing() { - ThemeManager tm = getThemeManager(); - if (tm != null) { - final String pkgName = mBaseThemePkgName != null ? mBaseThemePkgName : mPkgName; - return tm.isThemeBeingProcessed(pkgName); - } - return false; - } - - protected boolean onPopupMenuItemClick(MenuItem item) { - switch(item.getItemId()) { - /* TODO: Add back in once there is UX available for this feature - case R.id.menu_author: - Toast.makeText(getActivity(), - "Not supported", - Toast.LENGTH_LONG).show(); - break; - */ - case R.id.menu_delete: - showDeleteThemeOverlay(); - break; - } - - return true; - } - - public void expand() { - if (mCurrentLoaderId == LOADER_ID_ALL && mCurrentCursor != null) { - loadAdditionalCards(mCurrentCursor); - // we don't need this now that the additional cards are loaded, and - // we don't want to re-load these cards if the we expand again. - mCurrentCursor = null; - } - mClickableView.setVisibility(View.GONE); - mScrollView.setScrollingEnabled(true); - // Full width and height! - ViewGroup content = (ViewGroup) mScrollView.getParent(); - content.setPadding(0, 0, 0, 0); - ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; - mPreviewContent.setLayoutParams(layoutParams); - mScrollView.setPadding(0,0,0,0); - - // The parent of the wallpaper squishes the wp slightly because of padding from the 9 patch - // When the parent expands, the wallpaper returns to regular size which creates an - // undesireable effect. - Rect padding = new Rect(); - NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); - bg.getPadding(padding); - mIconCard.setPadding(padding.left, padding.top, padding.right, padding.bottom); - mShadowFrame.setBackground(null); - mShadowFrame.setPadding(0, 0, 0, 0); - - // Off screen cards will become visible and then be animated in - mWallpaperCard.setVisibility(View.VISIBLE); - - // Expand the children - int top = (int) getResources() - .getDimension(R.dimen.expanded_card_margin_top); - for (int i = 0; i < mPreviewContent.getChildCount(); i++) { - ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); - - LinearLayout.LayoutParams lparams = - (LinearLayout.LayoutParams) child.getLayoutParams(); - if (child == mStatusBarCard) { - int statusBarHeight = getResources() - .getDimensionPixelSize(R.dimen.status_bar_height); - lparams.setMargins(0, top + statusBarHeight, 0, 0); - } else { - lparams.setMargins(0, top, 0, 0); - } - - child.setLayoutParams(lparams); - child.expand(false); - } - - // Expand the additional children. - mAdditionalCards.setVisibility(View.VISIBLE); - for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { - View v = mAdditionalCards.getChildAt(i); - if (v instanceof ComponentCardView) { - ComponentCardView card = (ComponentCardView) v; - card.setVisibility(View.VISIBLE); - card.expand(true); - } - } - - // Collect the present position of all the children. The next layout/draw cycle will - // change these bounds since we just expanded them. Then we can animate from prev location - // to the new location. Note that the order of these calls matter as they all - // add themselves to the root layout as overlays - mScrollView.requestLayout(); - animateWallpaperOut(); - animateTitleCard(true, false); - animateChildren(true, getChildrensGlobalBounds(mPreviewContent)); - animateExtras(true); - mSelector = getChooserActivity().getComponentSelector(); - mSelector.setOnItemClickedListener(mOnComponentItemClicked); - if (mBootAnimation != null) mBootAnimation.start(); - hideThemeTagLayout(); - mExpanded = true; - } - - - - // Returns the boundaries for all the children of parent relative to the app window - private List getChildrensGlobalBounds(ViewGroup parent) { - List bounds = new ArrayList(); - for (int i = 0; i < parent.getChildCount(); i++) { - final View v = parent.getChildAt(i); - int[] pos = new int[2]; - v.getLocationInWindow(pos); - Rect boundary = new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1]+v.getHeight()); - bounds.add(boundary); - } - return bounds; - } - - public void performClick(boolean clickedOnContent) { - // Don't do anything if the theme is being processed - if (mProcessingThemeLayout.getVisibility() == View.VISIBLE) return; - - if (clickedOnContent) { - showApplyThemeOverlay(); - } else { - if (isShowingConfirmCancelOverlay()) { - hideConfirmCancelOverlay(); - } - } - } - - public void fadeOutCards(Runnable endAction) { - for (int i = 0; i < mPreviewContent.getChildCount(); i++) { - ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); - v.animateFadeOut(); - } - mHandler.postDelayed(endAction, ComponentCardView.CARD_FADE_DURATION); - } - - public void collapse(final boolean applyTheme) { - mScrollView.setScrollingEnabled(false); - - // Pad the view so it appears thinner - ViewGroup content = (ViewGroup) mScrollView.getParent(); - Resources r = mScrollView.getContext().getResources(); - int leftRightPadding = (int) r.getDimension(R.dimen.collapsed_theme_page_padding); - content.setPadding(leftRightPadding, 0, leftRightPadding, 0); - - if (applyTheme) { - final boolean customized = isThemeCustomized(); - mThemeTagLayout.setCustomizedTagEnabled(customized); - mReset.setVisibility(customized ? View.VISIBLE : View.GONE); - } - - //Move the theme preview so that it is near the center of page per spec - int paddingTop = (int) r.getDimension(R.dimen.collapsed_theme_page_padding_top); - if (!Utils.hasNavigationBar(getActivity())) { - paddingTop += - r.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; - } - mScrollView.setPadding(0, paddingTop, 0, 0); - - // During expand the wallpaper size decreases slightly to makeup for 9patch padding - // so when we collapse we should increase it again. - mShadowFrame.setBackgroundResource(R.drawable.bg_themepreview_shadow); - Rect padding = new Rect(); - final NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); - bg.getPadding(padding); - mShadowFrame.setPadding(padding.left, padding.top, padding.right, padding.bottom); - - // Gradually fade the drop shadow back in or else it will be out of place - ValueAnimator shadowAnimation = ValueAnimator.ofObject(new IntEvaluator(), 0, 255); - shadowAnimation.setDuration(ANIMATE_DURATION); - shadowAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - bg.setAlpha((Integer) animator.getAnimatedValue()); - } - - }); - shadowAnimation.start(); - - //Move the title card back in - mTitleCard.setVisibility(View.VISIBLE); - mTitleCard.setTranslationY(0); - - // Shrink the height - ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); - Resources resources = mPreviewContent.getResources(); - layoutParams.height = (int) resources.getDimension(R.dimen.theme_preview_height); - - mScrollView.requestLayout(); - List bounds = getChildrensGlobalBounds(mPreviewContent); - for (int i = 0; i < mPreviewContent.getChildCount(); i++) { - ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); - LinearLayout.LayoutParams lparams = - (LinearLayout.LayoutParams) child.getLayoutParams(); - lparams.setMargins(0, 0, 0, 0); - - if (child.getId() == R.id.icon_container) { - int top = (int) child.getResources() - .getDimension(R.dimen.collapsed_icon_card_margin_top); - lparams.setMargins(0, top, 0, 0); - } else if (child.getId() == R.id.font_preview_container) { - int top = (int) child.getResources() - .getDimension(R.dimen.collapsed_font_card_margin_top); - lparams.setMargins(0, top, 0, 0); - } else if (child.getId() == R.id.navigation_bar_container) { - int top = (int) child.getResources() - .getDimension(R.dimen.collapsed_navbar_card_margin_top); - lparams.setMargins(0, top, 0, 0); - } - - child.getLayoutParams(); - child.collapse(); - } - - // Collapse additional cards - for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { - View v = mAdditionalCards.getChildAt(i); - if (v instanceof ComponentCardView) { - ComponentCardView card = (ComponentCardView) v; - card.setVisibility(View.VISIBLE); - card.collapse(); - } - } - - animateChildren(false, bounds); - animateExtras(false); - animateWallpaperIn(); - animateTitleCard(false, applyTheme); - if (mBootAnimation != null) mBootAnimation.stop(); - stopMediaPlayers(); - showThemeTagLayout(); - - // Need to set the wallpaper background to black if the user has selected to apply - // the "none" wallpaper - if (applyTheme) { - String pkgName = mSelectedComponentsMap.get(ThemesColumns.MODIFIES_LAUNCHER); - if (pkgName != null && pkgName.length() == 0) { - mWallpaper.setImageResource(R.drawable.wallpaper_none_bg); - } - // we do this here instead of in applyTheme() because this can take a bit longer - // to propagate the change from WallpaperManager back to us - if (mExternalWallpaperUri != null) { - // Handle setting an external wallpaper in a separate thread - new Thread(mApplyExternalWallpaperRunnable).start(); - } - } - mExpanded = false; - } - - // This will animate the children's vertical positions between the previous bounds and the - // new bounds which occur on the next draw - private void animateChildren(final boolean isExpanding, final List prevBounds) { - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - final Resources res = getResources(); - final float yOffset = - res.getDimensionPixelSize(R.dimen.expand_collapse_child_offset) - * (isExpanding ? -1 : 1); - // Grab the child's new location and animate from prev to current loc. - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - - for (int i = mPreviewContent.getChildCount() - 1; i >= 0; i--) { - final ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); - - float prevY; - float endY; - float prevHeight; - float endHeight; - if (i >= prevBounds.size()) { - // View is being created - prevY = mPreviewContent.getTop() + mPreviewContent.getHeight(); - endY = v.getY(); - prevHeight = v.getHeight(); - endHeight = v.getHeight(); - } else { - Rect boundary = prevBounds.get(i); - prevY = boundary.top; - prevHeight = boundary.height(); - - int[] endPos = new int[2]; - v.getLocationInWindow(endPos); - endY = endPos[1]; - endHeight = v.getHeight(); - } - - int paddingTop = v.getPaddingTop() / 2; - float dy = (prevY - endY - paddingTop) + (prevHeight - endHeight) / 2; - dy += yOffset; - v.setTranslationY(dy); - root.getOverlay().add(v); - - // Expanding has a delay while the wallpaper begins to fade out - // Collapsing is opposite of this so wallpaper will have the delay instead - int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; - - v.animate() - .setStartDelay(startDelay) - .translationY(0) - .setDuration(ANIMATE_DURATION) - .setInterpolator( - new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) - .withEndAction(new Runnable() { - public void run() { - root.getOverlay().remove(v); - mPreviewContent.addView(v, 0); - } - }); - v.postDelayed(new Runnable() { - public void run() { - if (isExpanding) { - v.animateExpand(); - } - } - }, ANIMATE_DURATION / 2); - } - return true; - } - }); - } - - private void animateExtras(final boolean isExpanding) { - int[] pos = new int[2]; - mAdditionalCards.getLocationInWindow(pos); - final ViewGroup parent = (ViewGroup) mAdditionalCards.getParent(); - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - // During a collapse we don't want the card to shrink so add it to the overlay now - // During an expand we want the card to expand so add it to the overlay post-layout - if (!isExpanding) { - root.getOverlay().add(mAdditionalCards); - } - - // Expanding has a delay while the wallpaper begins to fade out - // Collapsing is opposite of this so wallpaper will have the delay instead - final int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - - int translationY = 0; - if (isExpanding) { - root.getOverlay().add(mAdditionalCards); - } else { - translationY = getDistanceToMoveBelowScreen(mAdditionalCards); - } - - int duration = isExpanding ? ANIMATE_DURATION + 100 : ANIMATE_DURATION; - mAdditionalCards.animate() - .setStartDelay(startDelay) - .translationY(translationY) - .setDuration(duration) - .setInterpolator( - new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) - .withEndAction(new Runnable() { - public void run() { - if (!isExpanding) { - mAdditionalCards.setVisibility(View.INVISIBLE); - } - root.getOverlay().remove(mAdditionalCards); - parent.addView(mAdditionalCards); - } - }); - return false; - } - }); - } - - private int getDistanceToMoveBelowScreen(View v) { - Display display = getActivity().getWindowManager().getDefaultDisplay(); - Point p = new Point(); - display.getSize(p); - int heightId = getResources() - .getIdentifier("navigation_bar_height", "dimen", "android"); - int navbar_height = getResources().getDimensionPixelSize(heightId); - int[] pos = new int[2]; - v.getLocationInWindow(pos); - return p.y + navbar_height - pos[1]; - } - - private void animateTitleCard(final boolean expand, final boolean applyTheme) { - final ViewGroup parent = (ViewGroup) mTitleCard.getParent(); - // Get current location of the title card - int[] location = new int[2]; - mTitleCard.getLocationOnScreen(location); - final int prevY = location[1]; - final int position = parent.indexOfChild(mTitleCard); - - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - root.getOverlay().add(mTitleCard); - - //Move title card back where it was before the relayout - float alpha = 1f; - if (expand) { - int[] endPos = new int[2]; - mTitleCard.getLocationInWindow(endPos); - int endY = endPos[1]; - mTitleCard.setTranslationY(prevY - endY); - alpha = 0; - } else { - } - - // Fade the title card and move it out of the way - mTitleCard.animate() - .alpha(alpha) - .setDuration(ANIMATE_DURATION) - .withEndAction(new Runnable() { - public void run() { - root.getOverlay().remove(mTitleCard); - parent.addView(mTitleCard, position); - if (expand) { - mTitleCard.setVisibility(View.INVISIBLE); - } else { - mTitleCard.setVisibility(View.VISIBLE); - mClickableView.setVisibility(View.VISIBLE); - if (applyTheme) { - // The title card is the last animation when collapsing so - // we will handle applying the theme, if applicable, here - applyTheme(); - } - } - } - }); - return true; - } - }); - } - - private void animateWallpaperOut() { - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - int[] location = new int[2]; - mWallpaper.getLocationOnScreen(location); - - final int prevY = location[1]; - - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - root.getOverlay().add(mWallpaper); - - int[] location = new int[2]; - mWallpaper.getLocationOnScreen(location); - final int newY = location[1]; - - mWallpaper.setTranslationY(prevY - newY); - mWallpaper.animate() - .alpha(0f) - .setDuration(300) - .withEndAction(new Runnable() { - public void run() { - root.getOverlay().remove(mWallpaper); - mShadowFrame.addView(mWallpaper, 0); - mWallpaper.setVisibility(View.GONE); - } - }); - return true; - - } - }); - } - - private void animateWallpaperIn() { - mWallpaper.setVisibility(View.VISIBLE); - mWallpaper.setTranslationY(0); - mWallpaper.animate() - .alpha(1f) - .setDuration(300); - } - - protected String getAppliedFontPackageName() { - final Configuration config = getActivity().getResources().getConfiguration(); - final ThemeConfig themeConfig = config != null ? config.themeConfig : null; - return themeConfig != null ? themeConfig.getFontPkgName() : - ThemeConfig.getSystemTheme().getFontPkgName(); - } - - protected ThemeManager getThemeManager() { - return ThemeManager.getInstance(getActivity()); - } - - private void freeMediaPlayers() { - for (MediaPlayer mp : mMediaPlayers.values()) { - if (mp != null) { - mp.stop(); - mp.release(); - } - } - mMediaPlayers.clear(); - } - - protected View.OnClickListener mPlayPauseClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - MediaPlayer mp = (MediaPlayer) v.getTag(); - if (mp != null) { - if (mp.isPlaying()) { - ((ImageView) v).setImageResource(R.drawable.media_sound_preview); - mp.pause(); - mp.seekTo(0); - } else { - stopMediaPlayers(); - ((ImageView) v).setImageResource(R.drawable.media_sound_stop); - mp.start(); - } - } - } - }; - - protected MediaPlayer.OnCompletionListener mPlayCompletionListener - = new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - for (ImageView v : mMediaPlayers.keySet()) { - if (mp == mMediaPlayers.get(v)) { - if (v != null) { - v.setImageResource(R.drawable.media_sound_preview); - } - } - } - } - }; - - private void stopMediaPlayers() { - for (ImageView v : mMediaPlayers.keySet()) { - if (v != null) { - v.setImageResource(R.drawable.media_sound_preview); - } - MediaPlayer mp = mMediaPlayers.get(v); - if (mp != null && mp.isPlaying()) { - mp.pause(); - mp.seekTo(0); - } - } - } - - protected void resetTheme() { - mSelectedComponentsMap.clear(); - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, mBaseThemePkgName); - args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); - getLoaderManager().restartLoader(LOADER_ID_ALL, args, this); - mThemeResetting = true; - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - String pkgName = mPkgName; - long componentId = DEFAULT_COMPONENT_ID; - if (args != null) { - pkgName = args.getString(ARG_PACKAGE_NAME); - componentId = args.getLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); - } - return CursorLoaderHelper.themeFragmentCursorLoader(getActivity(), id, pkgName, - componentId); - } - - @Override - public void onLoadFinished(Loader loader, Cursor c) { - if (c.getCount() == 0) return; - mCurrentCursor = c; - mCurrentLoaderId = loader.getId(); - c.moveToFirst(); - boolean animate = !mApplyThemeOnPopulated; - switch (mCurrentLoaderId) { - case LOADER_ID_ALL: - if (mProcessingResources && !isThemeProcessing()) { - mProcessingResources = false; - hideProcessingOverlay(); - } - loadLegacyThemeInfo(c); - populateSupportedComponents(c); - loadWallpaper(c, false); - loadStatusBar(c, false); - loadIcons(c, false); - loadNavBar(c, false); - loadTitle(c); - loadFont(c, false); - mHandler.post(new Runnable() { - @Override - public void run() { - animateContentIn(); - } - }); - if (mShowLockScreenSelectorAfterContentLoaded) { - getChooserActivity().expandContentAndAnimateLockScreenCardIn(); - mShowLockScreenSelectorAfterContentLoaded = false; - } - break; - case LOADER_ID_STATUS_BAR: - loadStatusBar(c, animate); - break; - case LOADER_ID_FONT: - loadFont(c, animate); - break; - case LOADER_ID_ICONS: - loadIcons(c, animate); - break; - case LOADER_ID_WALLPAPER: - loadWallpaper(c, animate); - break; - case LOADER_ID_NAVIGATION_BAR: - loadNavBar(c, animate); - break; - case LOADER_ID_LIVE_LOCK_SCREEN: - case LOADER_ID_LOCKSCREEN: - loadLockScreen(c, animate); - break; - case LOADER_ID_STYLE: - loadStyle(c, animate); - break; - case LOADER_ID_BOOT_ANIMATION: - loadBootAnimation(c); - break; - case LOADER_ID_RINGTONE: - loadAudible(RingtoneManager.TYPE_RINGTONE, c, animate); - break; - case LOADER_ID_NOTIFICATION: - loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, animate); - break; - case LOADER_ID_ALARM: - loadAudible(RingtoneManager.TYPE_ALARM, c, animate); - break; - } - if (mCurrentLoaderId != LOADER_ID_ALL) { - if (!componentsChanged()) { - getChooserActivity().hideSaveApplyButton(); - } else if (!mApplyThemeOnPopulated) { - getChooserActivity().showSaveApplyButton(); - } - } - } - - @Override - public void onLoaderReset(Loader loader) {} - - private void loadAdditionalCards(Cursor c) { - for(int i=0; i < mAdditionalCards.getChildCount(); i++) { - View v = mAdditionalCards.getChildAt(i); - if (v instanceof ComponentCardView) { - String component = mCardIdsToComponentTypes.get(v.getId()); - loadAdditionalCard(c, component, shouldShowComponentCard(component)); - } - } - } - - private void loadAdditionalCard(Cursor c, String component, boolean hasContent) { - if (MODIFIES_LOCKSCREEN.equals(component)) { - if (hasContent) { - loadLockScreen(c, false); - } else { - mLockScreenCard.clearWallpaper(); - mLockScreenCard.setEmptyViewEnabled(true); - setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); - } - } else if (MODIFIES_LAUNCHER.equals(component)) { - // this was already loaded so no need to do this again. - } else if (MODIFIES_OVERLAYS.equals(component)) { - if (hasContent) { - loadStyle(c, false); - } else { - mStyleCard.setEmptyViewEnabled(true); - setAddComponentTitle(mStyleCard, - getString(R.string.style_label)); - } - } else if (MODIFIES_BOOT_ANIM.equals(component)) { - if (hasContent) { - loadBootAnimation(c); - } else { - mBootAnimationCard.setEmptyViewEnabled(true); - setAddComponentTitle(mBootAnimationCard, - getString(R.string.boot_animation_label)); - } - } else if (MODIFIES_RINGTONES.equals(component)) { - if (hasContent) { - loadAudible(RingtoneManager.TYPE_RINGTONE, c, false); - } else { - mRingtoneCard.setEmptyViewEnabled(true); - setAddComponentTitle(mRingtoneCard, - getAudibleLabel(RingtoneManager.TYPE_RINGTONE)); - } - } else if (MODIFIES_NOTIFICATIONS.equals(component)) { - if (hasContent) { - loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, false); - } else { - mNotificationCard.setEmptyViewEnabled(true); - setAddComponentTitle(mNotificationCard, - getAudibleLabel(RingtoneManager.TYPE_NOTIFICATION)); - } - } else if (MODIFIES_ALARMS.equals(component)) { - if (hasContent) { - loadAudible(RingtoneManager.TYPE_ALARM, c, false); - } else { - mAlarmCard.setEmptyViewEnabled(true); - setAddComponentTitle(mAlarmCard, - getAudibleLabel(RingtoneManager.TYPE_ALARM)); - } - } else { - throw new IllegalArgumentException("Don't know how to load: " + component); - } - } - - protected void populateSupportedComponents(Cursor c) { - List components = ThemeUtils.getAllComponents(); - for(String component : components) { - int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int modifiesCompIdx = c.getColumnIndex(component); - - String pkg = c.getString(pkgIdx); - boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); - if (supported) { - mBaseThemeSupportedComponents.add(component); - mSelectedComponentsMap.put(component, pkg); - } - } - - if (mApplyThemeOnPopulated) { - applyTheme(); - } - } - - /** - * Determines whether a card should be shown or not. - * UX Rules: - * 1) "My Theme" always shows all cards - * 2) Other themes only show what has been implemented in the theme - * - */ - protected Boolean shouldShowComponentCard(String component) { - String pkg = mSelectedComponentsMap.get(component); - return pkg != null && pkg.equals(mPkgName); - } - - protected void loadLegacyThemeInfo(Cursor c) { - int targetApiIdx = c.getColumnIndex(ThemesColumns.TARGET_API); - // If this is being called for a MyThemeFragment the index will be -1 so set to - // SYSTEM_TARGET_API so we don't display the tag. If the user applied a legacy theme - // then they should have already been warned. - int targetApi = targetApiIdx < 0 ? SYSTEM_TARGET_API : c.getInt(targetApiIdx); - mIsLegacyTheme = targetApi != SYSTEM_TARGET_API && targetApi <= Build.VERSION_CODES.KITKAT; - mThemeTagLayout.setLegacyTagEnabled(mIsLegacyTheme); - } - - protected void loadTitle(Cursor c) { - int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); - int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); - mTitle.setText(c.getString(titleIdx)); - mAuthor.setText(c.getString(authorIdx)); - } - - protected void loadWallpaper(Cursor c, boolean animate) { - mExternalWallpaperUri = null; - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mWallpaperCard, true); - } - if (mWallpaperCard.isShowingEmptyView()) mWallpaperCard.setEmptyViewEnabled(false); - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); - int cmpntIdIdx = c.getColumnIndex(PreviewColumns.COMPONENT_ID); - final Resources res = getResources(); - Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); - if (bitmap != null) { - mWallpaper.setImageBitmap(bitmap); - mWallpaperCard.setWallpaper(new BitmapDrawable(res, bitmap)); - String pkgName = c.getString(pkgNameIdx); - Long cmpntId = (cmpntIdIdx >= 0) ? c.getLong(cmpntIdIdx) : DEFAULT_COMPONENT_ID; - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_LAUNCHER))) { - mSelectedComponentsMap.put(MODIFIES_LAUNCHER, pkgName); - mSelectedWallpaperComponentId = cmpntId; - setCardTitle(mWallpaperCard, pkgName, getString(R.string.wallpaper_label)); - } - } else { - // Set the wallpaper to "None" - mWallpaperCard.setWallpaper(null); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, getString(R.string.wallpaper_label)); - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); - } - - if (animate) { - animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); - } - } - - protected void loadLockScreen(Cursor c, boolean animate) { - mExternalLockscreenUri = null; - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mLockScreenCard, true); - } - if (mLockScreenCard.isShowingEmptyView()) mLockScreenCard.setEmptyViewEnabled(false); - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int liveLockIndex = c.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); - boolean isLiveLockScreen = liveLockIndex >= 0 && c.getInt(liveLockIndex) == 1; - - int wpIdx = isLiveLockScreen - ? c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW) - : c.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_PREVIEW); - final Resources res = getResources(); - Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); - if (bitmap != null) { - mLockScreenCard.setWallpaper(new BitmapDrawable(res, bitmap)); - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && (mBaseThemeSupportedComponents.contains(MODIFIES_LOCKSCREEN) || - mBaseThemeSupportedComponents.contains(MODIFIES_LIVE_LOCK_SCREEN)))) { - if (isLiveLockScreen) { - mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, pkgName); - if (mCurrentTheme.containsKey(MODIFIES_LOCKSCREEN)) { - mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); - } else { - mSelectedComponentsMap.remove(MODIFIES_LOCKSCREEN); - } - setCardTitle(mLockScreenCard, pkgName, - getString(R.string.live_lock_screen_label)); - } else { - mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, pkgName); - if (mCurrentTheme.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { - mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); - } else { - mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); - } - setCardTitle(mLockScreenCard, pkgName, getString(R.string.lockscreen_label)); - } - } - } else { - // Set the lockscreen wallpaper to "None" - mLockScreenCard.setWallpaper(null); - setCardTitle(mLockScreenCard, WALLPAPER_NONE, getString(R.string.lockscreen_label)); - } - - if (animate) { - animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); - } - } - - protected void loadStatusBar(Cursor c, boolean animate) { - int backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - int wifiIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); - int wifiMarginIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END); - int bluetoothIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); - int signalIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); - int batteryIdx = c.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); - int clockColorIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR); - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - - Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); - Bitmap bluetoothIcon = Utils.loadBitmapBlob(c, bluetoothIdx); - Bitmap wifiIcon = Utils.loadBitmapBlob(c, wifiIdx); - Bitmap signalIcon = Utils.loadBitmapBlob(c, signalIdx); - Bitmap batteryIcon = Utils.loadBitmapBlob(c, batteryIdx); - int wifiMargin = wifiMarginIdx != -1 ? c.getInt(wifiMarginIdx) : DEFAULT_WIFI_MARGIN; - int clockTextColor = clockColorIdx != -1 ? c.getInt(clockColorIdx) : DEFAULT_CLOCK_COLOR; - - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mStatusBar, false); - } - if (mStatusBarCard.isShowingEmptyView()) mStatusBarCard.setEmptyViewEnabled(false); - - mStatusBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); - mBluetooth.setImageBitmap(bluetoothIcon); - mWifi.setImageBitmap(wifiIcon); - mSignal.setImageBitmap(signalIcon); - mBattery.setImageBitmap(batteryIcon); - mClock.setTextColor(clockTextColor); - - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) mWifi.getLayoutParams(); - params.setMarginEnd(wifiMargin); - mWifi.setLayoutParams(params); - - if (mBatteryStyle == 4) { - mBattery.setVisibility(View.GONE); - } else { - mBattery.setVisibility(View.VISIBLE); - } - mStatusBar.post(new Runnable() { - @Override - public void run() { - mStatusBar.invalidate(); - } - }); - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_STATUS_BAR))) { - mSelectedComponentsMap.put(MODIFIES_STATUS_BAR, pkgName); - setCardTitle(mStatusBarCard, pkgName, - getString(R.string.statusbar_label)); - } - } - if (animate) { - animateContentChange(R.id.status_bar_container, mStatusBar, overlay); - } - } - - protected void loadIcons(Cursor c, boolean animate) { - if (mIconCard.isShowingEmptyView()) { - mIconCard.setEmptyViewEnabled(false); - } - int[] iconIdx = new int[3]; - iconIdx[0] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); - iconIdx[1] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_2); - iconIdx[2] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_3); - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - - // Set the icons. If the provider does not have an icon preview then - // fall back to the default icon set - IconPreviewHelper helper = new IconPreviewHelper(getActivity(), ""); - ViewGroup iconContainer = - (ViewGroup) mIconCard.findViewById(R.id.icon_preview_container); - int numOfChildren = iconContainer.getChildCount(); - - List iconViews = new ArrayList(numOfChildren); - for(int i=0; i < numOfChildren; i++) { - final View view = iconContainer.getChildAt(i); - if (!(view instanceof ImageView)) continue; - iconViews.add((ImageView) view); - } - - for(int i=0; i < iconViews.size() && i < iconIdx.length; i++) { - final ImageView v = iconViews.get(i); - Bitmap bitmap = Utils.loadBitmapBlob(c, iconIdx[i]); - Drawable oldIcon = v.getDrawable(); - Drawable newIcon; - if (bitmap == null) { - ComponentName component = sIconComponents[i]; - newIcon = helper.getDefaultIcon(component.getPackageName(), - component.getClassName()); - } else { - newIcon = new BitmapDrawable(getResources(), bitmap); - } - if (animate) { - Drawable[] layers = new Drawable[2]; - layers[0] = oldIcon instanceof IconTransitionDrawable ? - ((IconTransitionDrawable) oldIcon).getDrawable(1) : oldIcon; - layers[1] = newIcon; - final IconTransitionDrawable itd = new IconTransitionDrawable(layers); - v.postDelayed(new Runnable() { - @Override - public void run() { - itd.startTransition(ANIMATE_COMPONENT_CHANGE_DURATION); - v.setImageDrawable(itd); - } - }, ANIMATE_COMPONENT_ICON_DELAY * i); - } else { - v.setImageDrawable(newIcon); - } - } - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_ICONS))) { - mSelectedComponentsMap.put(MODIFIES_ICONS, pkgName); - setCardTitle(mIconCard, pkgName, - getString(R.string.icon_label)); - } - } - } - - protected void loadNavBar(Cursor c, boolean animate) { - int backButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); - int homeButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_HOME_BUTTON); - int recentButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_RECENT_BUTTON); - int backgroundIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACKGROUND); - if (backgroundIdx == -1) { - backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - } - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - - Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); - Bitmap backButton = Utils.loadBitmapBlob(c, backButtonIdx); - Bitmap homeButton = Utils.loadBitmapBlob(c, homeButtonIdx); - Bitmap recentButton = Utils.loadBitmapBlob(c, recentButtonIdx); - - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mNavBar, false); - } - if (mNavBarCard.isShowingEmptyView()) mNavBarCard.setEmptyViewEnabled(false); - - mNavBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); - mBackButton.setImageBitmap(backButton); - mHomeButton.setImageBitmap(homeButton); - mRecentButton.setImageBitmap(recentButton); - - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_NAVIGATION_BAR))) { - mSelectedComponentsMap.put(MODIFIES_NAVIGATION_BAR, pkgName); - setCardTitle(mNavBarCard, pkgName, getString(R.string.navbar_label)); - } - } - if (animate) { - animateContentChange(R.id.navigation_bar_container, mNavBar, overlay); - } - } - - protected void loadFont(Cursor c, boolean animate) { - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mFontPreview, true); - } - if (mFontCard.isShowingEmptyView()) mFontCard.setEmptyViewEnabled(false); - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - String pkgName = pkgNameIdx >= 0 ? c.getString(pkgNameIdx) : mPkgName; - TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); - ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), pkgName); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - mFontPreview.setTypeface(mTypefaceNormal); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_FONTS))) { - mSelectedComponentsMap.put(MODIFIES_FONTS, pkgName); - setCardTitle(mFontCard, pkgName, getString(R.string.font_label)); - } - - if (animate) { - animateContentChange(R.id.font_preview_container, mFontPreview, overlay); - } - } - - protected void loadStyle(Cursor c, boolean animate) { - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mStylePreview, true); - } - if (mStyleCard.isShowingEmptyView()) { - mStyleCard.setEmptyViewEnabled(false); - } - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int styleIdx = c.getColumnIndex(PreviewColumns.STYLE_PREVIEW); - mStylePreview.setImageBitmap(Utils.loadBitmapBlob(c, styleIdx)); - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_OVERLAYS))) { - mSelectedComponentsMap.put(MODIFIES_OVERLAYS, pkgName); - setCardTitle(mStyleCard, pkgName, - getString(R.string.style_label)); - } - } - if (animate) { - animateContentChange(R.id.style_card, mStylePreview, overlay); - } - } - - protected void loadBootAnimation(Cursor c) { - if (mBootAnimationCard.isShowingEmptyView()) { - mBootAnimationCard.setEmptyViewEnabled(false); - } - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - if (mBootAnimation != null) { - String pkgName; - if (pkgNameIdx > -1) { - pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_BOOT_ANIM))) { - mSelectedComponentsMap.put(MODIFIES_BOOT_ANIM, pkgName); - setCardTitle(mBootAnimationCard, pkgName, - getString(R.string.boot_animation_label)); - } - } else { - pkgName = mCurrentTheme.get(MODIFIES_BOOT_ANIM); - } - mBootAnimation.stop(); - new AnimationLoader(getActivity(), pkgName, mBootAnimation).execute(); - } - } - - protected void loadAudible(int type, Cursor c, boolean animate) { - ComponentCardView audibleContainer = null; - ImageView playPause = null; - String component = null; - int parentResId = 0; - switch (type) { - case RingtoneManager.TYPE_RINGTONE: - audibleContainer = mRingtoneCard; - playPause = mRingtonePlayPause; - component = MODIFIES_RINGTONES; - parentResId = R.id.ringtone_preview_container; - break; - case RingtoneManager.TYPE_NOTIFICATION: - audibleContainer = mNotificationCard; - playPause = mNotificationPlayPause; - component = MODIFIES_NOTIFICATIONS; - parentResId = R.id.notification_preview_container; - break; - case RingtoneManager.TYPE_ALARM: - audibleContainer = mAlarmCard; - playPause = mAlarmPlayPause; - component = MODIFIES_ALARMS; - parentResId = R.id.alarm_preview_container; - break; - } - if (audibleContainer == null) return; - - View content = audibleContainer.findViewById(R.id.content); - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(content, true); - } - if (audibleContainer.isShowingEmptyView()) { - audibleContainer.setEmptyViewEnabled(false); - } - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); - if (playPause == null) { - playPause = (ImageView) audibleContainer.findViewById(R.id.play_pause); - } - TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); - MediaPlayer mp = mMediaPlayers.get(playPause); - if (mp == null) { - mp = new MediaPlayer(); - } - String pkgName = c.getString(pkgNameIdx); - setCardTitle(audibleContainer, pkgName, getAudibleLabel(type)); - AudibleLoadingThread thread = new AudibleLoadingThread(getActivity(), type, pkgName, mp); - title.setText(c.getString(titleIdx)); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(component))) { - mSelectedComponentsMap.put(component, pkgName); - } - - playPause.setVisibility(View.VISIBLE); - playPause.setTag(mp); - mMediaPlayers.put(playPause, mp); - playPause.setOnClickListener(mPlayPauseClickListener); - mp.setOnCompletionListener(mPlayCompletionListener); - if (animate) { - animateContentChange(parentResId, content, overlay); - } - thread.start(); - } - - protected Drawable getOverlayDrawable(View v, boolean requiresTransparency) { - if (!v.isDrawingCacheEnabled()) v.setDrawingCacheEnabled(true); - Bitmap cache = v.getDrawingCache(true).copy( - requiresTransparency ? Config.ARGB_8888 : Config.RGB_565, false); - Drawable d = cache != null ? new BitmapDrawable(getResources(), cache) : null; - v.destroyDrawingCache(); - - return d; - } - - protected String getAudibleLabel(int type) { - switch (type) { - case RingtoneManager.TYPE_RINGTONE: - return getString(R.string.ringtone_label); - case RingtoneManager.TYPE_NOTIFICATION: - return getString(R.string.notification_label); - case RingtoneManager.TYPE_ALARM: - return getString(R.string.alarm_label); - } - return null; - } - - protected void setCardTitle(ComponentCardView card, String pkgName, String title) { - TextView tv = (TextView) card.findViewById(R.id.label); - if (Utils.getDefaultThemePackageName(getActivity()).equals(pkgName)) { - tv.setText(getString(R.string.default_tag_text) + " " + title); - } else { - tv.setText(title); - } - } - - protected void setAddComponentTitle(ComponentCardView card, String title) { - TextView tv = (TextView) card.findViewById(R.id.label); - tv.setText(getString(R.string.add_component_text) + " " + title); - } - - public static ComponentName[] getIconComponents(Context context) { - if (sIconComponents == null || sIconComponents.length == 0) { - sIconComponents = new ComponentName[]{COMPONENT_DIALER, COMPONENT_MESSAGING, - COMPONENT_CAMERA, COMPONENT_BROWSER}; - - PackageManager pm = context.getPackageManager(); - - // if device does not have telephony replace dialer and mms - if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { - sIconComponents[0] = COMPONENT_CALENDAR; - sIconComponents[1] = COMPONENT_GALERY; - } else { - // decide on which dialer icon to use - try { - if (pm.getPackageInfo(DIALER_NEXT_PACKAGE, 0) != null) { - sIconComponents[0] = COMPONENT_DIALERNEXT; - } - } catch (PackageManager.NameNotFoundException e) { - // default to COMPONENT_DIALER - } - } - - if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) { - sIconComponents[2] = COMPONENT_SETTINGS; - } else { - // decide on which camera icon to use - try { - if (pm.getPackageInfo(CAMERA_NEXT_PACKAGE, 0) != null) { - sIconComponents[2] = COMPONENT_CAMERANEXT; - } - } catch (PackageManager.NameNotFoundException e) { - // default to COMPONENT_CAMERA - } - } - - } - return sIconComponents; - } - - private void setupCardClickListeners(View parent) { - for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { - parent.findViewById(mCardIdsToComponentTypes.keyAt(i)) - .setOnClickListener(mCardClickListener); - } - } - - private View.OnClickListener mCardClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (isShowingConfirmCancelOverlay() || isShowingCustomizeResetLayout()) return; - if (mActiveCardId > 0) { - // need to fade the newly selected card in if another was currently selected. - ((ComponentCardView) v).animateCardFadeIn(); - } - mActiveCardId = v.getId(); - String component = mCardIdsToComponentTypes.get(mActiveCardId); - // Only pass on mSelectedWallpaperComponentId if dealing with mods_launcher - long selectedComponentId = (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) ? - mSelectedWallpaperComponentId : DEFAULT_COMPONENT_ID; - String pkgName = mSelectedComponentsMap.get(component); - if (component.equals(MODIFIES_LOCKSCREEN) && TextUtils.isEmpty(pkgName)) { - String liveLockScreenPkg = mSelectedComponentsMap.get(MODIFIES_LIVE_LOCK_SCREEN); - if (liveLockScreenPkg != null) { - pkgName = liveLockScreenPkg; - } - } - getChooserActivity().showComponentSelector(component, pkgName, selectedComponentId, v); - fadeOutNonSelectedCards(mActiveCardId); - stopMediaPlayers(); - } - }; - - private ConfirmCancelOverlay.OnOverlayDismissedListener mApplyCancelListener = - new ConfirmCancelOverlay.OnOverlayDismissedListener() { - @Override - public void onDismissed(boolean accepted) { - hideConfirmCancelOverlay(accepted); - } - }; - - private ConfirmCancelOverlay.OnOverlayDismissedListener mDeleteConfirmationListener = - new ConfirmCancelOverlay.OnOverlayDismissedListener() { - @Override - public void onDismissed(boolean accepted) { - if (accepted) uninstallTheme(); - hideConfirmCancelOverlay(); - } - }; - - private ConfirmCancelOverlay.OnOverlayDismissedListener mResetConfirmationListener = - new ConfirmCancelOverlay.OnOverlayDismissedListener() { - @Override - public void onDismissed(boolean accepted) { - if (accepted) resetTheme(); - hideConfirmCancelOverlay(); - } - }; - - private View.OnClickListener mCustomizeResetClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (v == mDismissButton) { - hideCustomizeResetLayout(CustomizeResetAction.Dismiss); - } else if (v == mResetButton) { - hideCustomizeResetLayout(CustomizeResetAction.Reset); - } else if (v == mCustomizeButton) { - hideCustomizeResetLayout(CustomizeResetAction.Customize); - } - } - }; - - protected void loadComponentFromPackage(String pkgName, String component, long componentId) { - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, pkgName); - args.putLong(ARG_COMPONENT_ID, componentId); - int loaderId = LOADER_ID_INVALID; - if (MODIFIES_STATUS_BAR.equals(component)) { - loaderId = LOADER_ID_STATUS_BAR; - } else if (MODIFIES_FONTS.equals(component)) { - loaderId = LOADER_ID_FONT; - } else if (MODIFIES_ICONS.equals(component)) { - loaderId = LOADER_ID_ICONS; - } else if (MODIFIES_NAVIGATION_BAR.equals(component)) { - loaderId = LOADER_ID_NAVIGATION_BAR; - } else if (MODIFIES_LAUNCHER.equals(component)) { - if (pkgName != null) { - if (TextUtils.isEmpty(pkgName)) { - mWallpaperCard.setWallpaper(null); - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, - getString(R.string.wallpaper_label)); - getChooserActivity().showSaveApplyButton(); - } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { - // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, - // otherwise let the user pick an image - if (getActivity().checkSelfPermission( - READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { - mAfterPermissionGrantedRunnable = new Runnable() { - @Override - public void run() { - getChooserActivity().pickExternalWallpaper(); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, - getString(R.string.wallpaper_label)); - } - }; - requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, - PERMISSION_REQUEST); - } else { - getChooserActivity().pickExternalWallpaper(); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, - getString(R.string.wallpaper_label)); - } - } else { - loaderId = LOADER_ID_WALLPAPER; - } - } - } else if (MODIFIES_LOCKSCREEN.equals(component) - || MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { - if (pkgName != null && TextUtils.isEmpty(pkgName)) { - mLockScreenCard.setWallpaper(null); - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); - - if(mSelectedComponentsMap.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { - mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); - } - setCardTitle(mLockScreenCard, WALLPAPER_NONE, - getString(R.string.lockscreen_label)); - if (mLockScreenCard.isShowingEmptyView()) { - mLockScreenCard.setEmptyViewEnabled(false); - } - getChooserActivity().showSaveApplyButton(); - } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { - // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, - // otherwise let the user pick an image - if (getActivity().checkSelfPermission( - READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { - mAfterPermissionGrantedRunnable = new Runnable() { - @Override - public void run() { - getChooserActivity().pickExternalLockscreen(); - setCardTitle(mLockScreenCard, WALLPAPER_NONE, - getString(R.string.lockscreen_label)); - } - }; - requestPermissions(new String[] {READ_EXTERNAL_STORAGE}, - PERMISSION_REQUEST); - } else { - getChooserActivity().pickExternalLockscreen(); - setCardTitle(mLockScreenCard, WALLPAPER_NONE, - getString(R.string.lockscreen_label)); - } - } else if (ComponentSelector.MOD_LOCK.equals(pkgName)) { - startLiveLockScreenSettings(); - } else { - if (MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { - loaderId = LOADER_ID_LIVE_LOCK_SCREEN; - } else { - loaderId = LOADER_ID_LOCKSCREEN; - } - } - } else if (MODIFIES_OVERLAYS.equals(component)) { - loaderId = LOADER_ID_STYLE; - } else if (MODIFIES_BOOT_ANIM.equals(component)) { - loaderId = LOADER_ID_BOOT_ANIMATION; - } else if (MODIFIES_RINGTONES.equals(component)) { - loaderId = LOADER_ID_RINGTONE; - } else if (MODIFIES_NOTIFICATIONS.equals(component)) { - loaderId = LOADER_ID_NOTIFICATION; - } else if (MODIFIES_ALARMS.equals(component)) { - loaderId = LOADER_ID_ALARM; - } else { - return; - } - - if (loaderId != LOADER_ID_INVALID) { - getLoaderManager().restartLoader(loaderId, args, ThemeFragment.this); - } - } - - private OnItemClickedListener mOnComponentItemClicked = new OnItemClickedListener() { - @Override - public void onItemClicked(String pkgName, long componentId, Bundle params) { - String component = mSelector.getComponentType(); - if (MODIFIES_LOCKSCREEN.equals(component) && params != null) { - boolean isLiveLockView = params.getBoolean( - ComponentSelector.IS_LIVE_LOCK_SCREEN_VIEW,false); - if (isLiveLockView) { - //We got here because an live lock thubmnail view was clicked. We need to - //replace the component to load the proper data from the provider. - component = MODIFIES_LIVE_LOCK_SCREEN; - } - } - loadComponentFromPackage(pkgName, component, componentId); - } - }; - - private void fadeOutNonSelectedCards(int selectedCardId) { - for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { - if (mCardIdsToComponentTypes.keyAt(i) != selectedCardId) { - ComponentCardView card = (ComponentCardView) getView().findViewById( - mCardIdsToComponentTypes.keyAt(i)); - if (card != null) card.animateCardFadeOut(); - } - } - } - - protected void animateContentChange(int parentId, View viewToAnimate, Drawable overlay) { - ((ComponentCardView) getView().findViewById(parentId)) - .animateContentChange(viewToAnimate, overlay, ANIMATE_COMPONENT_CHANGE_DURATION); - } - - private Runnable mApplyThemeRunnable = new Runnable() { - @Override - public void run() { - final Context context = getActivity(); - if (context != null) { - // Post this on mHandler so the client is added and removed from the same - // thread - mHandler.post(new Runnable() { - @Override - public void run() { - final Map componentsToApply = getComponentsToApply(); - if (componentsToApply != null && componentsToApply.size() > 0) { - final Map fullMap - = fillMissingComponentsWithDefault(componentsToApply); - ThemeManager tm = getThemeManager(); - if (tm != null) { - try { - tm.addClient(ThemeFragment.this); - } catch (IllegalArgumentException e) { - /* ignore since this means we already have a listener added */ - } - ThemeChangeRequest request = - getThemeChangeRequestForComponents(fullMap); - boolean value = request.getReqeustType(). - equals(RequestType.USER_REQUEST_MIXNMATCH); - - tm.requestThemeChange(request, !value); - } - mApplyThemeOnPopulated = false; - } else { - onFinish(true); - } - } - }); - } - } - }; - - protected Map fillMissingComponentsWithDefault( - Map originalMap) { - HashMap newMap = new HashMap(); - newMap.putAll(originalMap); - Map defaultMap = getEmptyComponentsMap(); - for(Map.Entry entry : defaultMap.entrySet()) { - String component = entry.getKey(); - String defaultPkg = entry.getValue(); - if (!newMap.containsKey(component)) { - newMap.put(component, defaultPkg); - } - } - return newMap; - } - - protected Map getEmptyComponentsMap() { - List componentsList = ThemeUtils.getAllComponents(); - Map defaultMap = new HashMap<>(componentsList.size()); - for (String component : componentsList) { - defaultMap.put(component, ""); - } - return defaultMap; - } - - /** - * This is the method that will be called when applying a theme and the idea is to override - * it in MyThemeFragment and pass in a different RequestType, once we have a type that indicates - * the user is mixing and matching instead of applying an entire theme. - * @param componentMap - * @return - */ - protected ThemeChangeRequest getThemeChangeRequestForComponents( - Map componentMap) { - return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST); - } - - protected ThemeChangeRequest getThemeChangeRequestForComponents( - Map componentMap, RequestType requestType) { - ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); - for (String component : componentMap.keySet()) { - builder.setComponent(component, componentMap.get(component)); - } - builder.setRequestType(requestType); - if (mThemeVersion >= 3) { - builder.setWallpaperId(mSelectedWallpaperComponentId != null - ? mSelectedWallpaperComponentId - : DEFAULT_COMPONENT_ID); - } - return builder.build(); - } - - protected Map getComponentsToApply() { - return mSelectedComponentsMap; - } - - private Runnable mApplyExternalWallpaperRunnable = new Runnable() { - @Override - public void run() { - // If an external image was selected for the wallpaper, we need to - // set that manually. - if (mExternalWallpaperUri != null) { - WallpaperManager wm = - WallpaperManager.getInstance(getActivity()); - final Context context = getActivity(); - final Resources res = context.getResources(); - final Point size = new Point(wm.getDesiredMinimumWidth(), - wm.getDesiredMinimumHeight()); - Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalWallpaperUri, - null, res, 0, 0, false); - try { - wm.setBitmap(bmp); - } catch (Exception e) { - Log.e(TAG, "Unable to set external wallpaper", e); - } - } - } - }; - - private Runnable mApplyExternalLockscreenRunnable = new Runnable() { - @Override - public void run() { - // If an external image was selected for the wallpaper, we need to - // set that manually. - if (mExternalLockscreenUri != null) { - WallpaperManager wm = - WallpaperManager.getInstance(getActivity()); - final Context context = getActivity(); - final Resources res = context.getResources(); - final Point size = new Point(); - ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(size); - Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalLockscreenUri, - null, res, 0, 0, false); - try { - wm.setKeyguardBitmap(bmp); - } catch (Exception e) { - Log.e(TAG, "Unable to set external lockscreen wallpaper", e); - } - } - } - }; - - class RestoreLockScreenCardRunnable implements Runnable { - - final private boolean mWasShowingNone; - private String mCurrentLockScreenPkgName; - - public RestoreLockScreenCardRunnable(boolean w, String pkgName) { - mWasShowingNone = w; - mCurrentLockScreenPkgName = pkgName; - } - - @Override - public void run() { - if (!TextUtils.isEmpty(mCurrentLockScreenPkgName)) { - loadComponentFromPackage(mCurrentLockScreenPkgName, - MODIFIES_LOCKSCREEN, 0); - } else if (mWasShowingNone) { - mLockScreenCard.setWallpaper(null); - TextView none = (TextView) mLockScreenCard.findViewById( - R.id.none); - if (none != null) { - none.setVisibility(View.VISIBLE); - } - mLockScreenCard.setEmptyViewEnabled(false); - } else { - mLockScreenCard.clearWallpaper(); - TextView none = (TextView) mLockScreenCard.findViewById( - R.id.none); - if (none != null) { - none.setVisibility(View.GONE); - } - mLockScreenCard.setEmptyViewEnabled(true); - setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); - } - } - } - - protected void applyTheme() { - if (mExternalWallpaperUri == null && mExternalLockscreenUri == null && - (mSelectedComponentsMap == null || mSelectedComponentsMap.size() <= 0)) { - return; - } - final Map componentsToApply = getComponentsToApply(); - boolean isLLSEnabled = CMSettings.Secure.getInt(getActivity().getContentResolver(), - LIVE_LOCK_SCREEN_ENABLED, 0) == 1; - if (!TextUtils.isEmpty(componentsToApply.get(MODIFIES_LIVE_LOCK_SCREEN)) && !isLLSEnabled) { - AlertDialog d = new AlertDialog.Builder(getActivity(), - android.R.style.Theme_Material_Dialog) - .setTitle(R.string.enable_live_lock_screen_dialog_title) - .setMessage(R.string.enable_live_lock_screen_dialog_message) - .setPositiveButton(R.string.enable_live_lock_screen_dialog_positive_btn_text, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - CMSettings.Secure.putInt(getActivity().getContentResolver(), - LIVE_LOCK_SCREEN_ENABLED, 1); - getChooserActivity().themeChangeStart(); - animateProgressIn(mApplyThemeRunnable); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); - boolean wasNone = false; - if (TextUtils.equals("", mSelectedComponentsMap.get( - MODIFIES_LOCKSCREEN))) { - if (!TextUtils.isEmpty(mCurrentTheme.get(MODIFIES_LOCKSCREEN))) { - //The map entry was set to empty string because there's a - //lockscreen currently applied and setting this entry to empty - //would instruct the ThemeManager to clear the currently applied - //lockscreen, but the user decided not to enable LLS so we need - //to abort this change - mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, - mCurrentTheme.get(MODIFIES_LOCKSCREEN)); - } else { - wasNone = true; - } - } - //Restore the lockscreen card to its previous state - mHandler.post(new RestoreLockScreenCardRunnable(wasNone, - mCurrentTheme.get(MODIFIES_LOCKSCREEN))); - - //Did the user make more changes? - boolean modLLS = componentsToApply.containsKey( - MODIFIES_LIVE_LOCK_SCREEN); - boolean modLockscreen = componentsToApply.containsKey( - MODIFIES_LOCKSCREEN); - if (modLLS && ((modLockscreen && componentsToApply.size() > 2) || - (!modLockscreen && componentsToApply.size() > 1))) { - getChooserActivity().themeChangeStart(); - animateProgressIn(mApplyThemeRunnable); - } - } - }) - .setCancelable(false) - .create(); - d.setCanceledOnTouchOutside(false); - d.show(); - } else { - getChooserActivity().themeChangeStart(); - animateProgressIn(mApplyThemeRunnable); - } - } - - /** - * Use when applyTheme() might be too early. ie mSelectedComponentsMap is not pop. yet - * @param pkgName Only used in MyThemeFragment to apply components on top of current theme - * @param components Optional list of components to apply. - */ - protected void applyThemeWhenPopulated(String pkgName, List components) { - mApplyThemeOnPopulated = true; - } - - private void animateProgressIn(Runnable endAction) { - mProgress.setVisibility(View.VISIBLE); - mProgress.setProgress(0); - float pivotX = mTitleLayout.getWidth() - - getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); - ScaleAnimation scaleAnim = new ScaleAnimation(0f, 1f, 1f, 1f, - pivotX, 0f); - scaleAnim.setDuration(ANIMATE_PROGRESS_IN_DURATION); - - mTitleLayout.animate() - .translationXBy(-(pivotX / 3)) - .alpha(0f) - .setDuration(ANIMATE_TITLE_OUT_DURATION) - .setInterpolator(new AccelerateInterpolator()) - .withEndAction(endAction).start(); - mProgress.startAnimation(scaleAnim); - } - - private void animateProgressOut() { - mProgress.setVisibility(View.VISIBLE); - float pivotX = mTitleLayout.getWidth() - - getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); - ScaleAnimation scaleAnim = new ScaleAnimation(1f, 0f, 1f, 1f, - pivotX, 0f); - scaleAnim.setDuration(ANIMATE_PROGRESS_OUT_DURATION); - scaleAnim.setFillAfter(false); - scaleAnim.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mProgress.setVisibility(View.GONE); - if (mThemeResetting) { - mThemeResetting = false; - mThemeTagLayout.setCustomizedTagEnabled(false); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - - mTitleLayout.animate() - .translationXBy((pivotX / 3)) - .alpha(1f) - .setDuration(ANIMATE_TITLE_IN_DURATION) - .setInterpolator(new AccelerateInterpolator()) - .start(); - mProgress.startAnimation(scaleAnim); - if (mThemeResetting) mReset.setVisibility(View.GONE); - } - - private void animateContentIn() { - if (mSkipLoadingAnim) { - return; - } - AnimatorSet set = new AnimatorSet(); - set.setDuration(ANIMATE_TITLE_IN_DURATION); - set.play(ObjectAnimator.ofFloat(mLoadingView, "alpha", 1f, 0f)) - .with(ObjectAnimator.ofFloat(mTitleLayout, "alpha", 0f, 1f)); - set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mLoadingView.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - set.start(); - } - - private void disableActionButtons() { - mCustomize.setEnabled(false); - mDelete.setEnabled(false); - mReset.setEnabled(false); - } - - private void enableActionButtons() { - mCustomize.setEnabled(true); - mDelete.setEnabled(true); - mReset.setEnabled(true); - } - - public boolean isShowingConfirmCancelOverlay() { - return mConfirmCancelOverlay.getVisibility() == View.VISIBLE; - } - - public void showApplyThemeOverlay() { - if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; - mConfirmCancelOverlay.setTitle(R.string.apply_theme_overlay_title); - mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() - .getColor(R.color.apply_overlay_background)); - mConfirmCancelOverlay.setOnOverlayDismissedListener(mApplyCancelListener); - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - mConfirmCancelOverlay.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - if (mIsLegacyTheme) { - // Display cm11 theme warning message - TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); - tv.setVisibility(View.VISIBLE); - tv.setText(String.format(getString(R.string.legacy_theme_warning), mTitle.getText())); - } else if (Utils.hasPerAppThemesApplied(getActivity())) { - // Display per app theme changes will be removed warning - TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); - tv.setVisibility(View.VISIBLE); - tv.setText(String.format(getString(R.string.per_app_theme_removal_warning), - mTitle.getText())); - } - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void showDeleteThemeOverlay() { - if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; - mConfirmCancelOverlay.setTitle(R.string.delete_theme_overlay_title); - mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() - .getColor(R.color.delete_overlay_background)); - mConfirmCancelOverlay.setOnOverlayDismissedListener(mDeleteConfirmationListener); - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - mConfirmCancelOverlay.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void showResetThemeOverlay() { - if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; - mConfirmCancelOverlay.setTitle(R.string.reset_theme_overlay_title); - mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() - .getColor(R.color.apply_overlay_background)); - mConfirmCancelOverlay.setOnOverlayDismissedListener(mResetConfirmationListener); - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - mConfirmCancelOverlay.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void hideConfirmCancelOverlay() { - hideConfirmCancelOverlay(false); - } - - /** - * Hides the apply theme layout overlay and can apply the selected theme - * when the animation is finished. - * @param applyThemeWhenFinished If true, the current theme will be applied. - */ - private void hideConfirmCancelOverlay(final boolean applyThemeWhenFinished) { - getChooserActivity().unlockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(0f).start(); - anim.setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mConfirmCancelOverlay.setVisibility(View.GONE); - if (applyThemeWhenFinished) applyTheme(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - - enableActionButtons(); - mClickableView.setSoundEffectsEnabled(true); - } - - public boolean isShowingCustomizeResetLayout() { - return mCustomizeResetLayout.getVisibility() == View.VISIBLE; - } - - public void showCustomizeResetLayout() { - if (mCustomizeResetLayout.getVisibility() == View.VISIBLE) return; - if (!mThemeTagLayout.isCustomizedTagEnabled()) { - mResetButton.setEnabled(false); - } else { - mResetButton.setEnabled(true); - } - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); - mCustomizeResetLayout.setVisibility(View.VISIBLE); - mCustomizeResetLayout.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void hideCustomizeResetLayout() { - hideCustomizeResetLayout(CustomizeResetAction.Dismiss); - } - - private void hideCustomizeResetLayout(final CustomizeResetAction action) { - getChooserActivity().unlockPager(); - ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); - mCustomizeResetLayout.setVisibility(View.VISIBLE); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(0f).start(); - anim.setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mCustomizeResetLayout.setVisibility(View.GONE); - switch (action) { - case Customize: - getChooserActivity().expand(); - break; - case Reset: - resetTheme(); - break; - } - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - - enableActionButtons(); - mClickableView.setSoundEffectsEnabled(true); - } - - public void showThemeTagLayout() { - mThemeTagLayout.setVisibility(View.VISIBLE); - mThemeTagLayout.animate().alpha(1f).setStartDelay(ANIMATE_START_DELAY).start(); - } - - public void hideThemeTagLayout() { - mThemeTagLayout.setAlpha(0f); - mThemeTagLayout.setVisibility(View.GONE); - } - - public void hideProcessingOverlay() { - mProcessingThemeLayout.animate().alpha(0).withEndAction(new Runnable() { - @Override - public void run() { - mProcessingThemeLayout.setVisibility(View.GONE); - } - }).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); - mCustomize.setVisibility(View.VISIBLE); - mCustomize.setAlpha(0f); - mCustomize.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); - if (mDelete.getVisibility() != View.GONE) { - mDelete.setVisibility(View.VISIBLE); - mDelete.setAlpha(0f); - mDelete.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); - } - - enableActionButtons(); - mClickableView.setSoundEffectsEnabled(true); - } - - public void fadeInCards() { - for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { - final int key = mCardIdsToComponentTypes.keyAt(i); - if (key != mActiveCardId) { - ComponentCardView card = (ComponentCardView) getView().findViewById(key); - if (card != null) card.animateCardFadeIn(); - } - } - mActiveCardId = -1; - } - - public boolean componentsChanged() { - // If an external wallpaper/ls are set then something changed! - if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; - - for (String key : mSelectedComponentsMap.keySet()) { - if (!mPkgName.equals(mSelectedComponentsMap.get(key))) { - return true; - } - if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && - mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { - return true; - } - } - return false; - } - - protected boolean isThemeCustomized() { - final String themePkgName = getThemePackageName(); - for (String key : mSelectedComponentsMap.keySet()) { - final String selectedPkgName = mSelectedComponentsMap.get(key); - if (!themePkgName.equals(selectedPkgName)) { - return true; - } - if (mBaseThemeSupportedComponents.size() > 0 && - !mBaseThemeSupportedComponents.contains(key)) { - return true; - } - } - // finally check if we're missing anything from mBaseThemeSupportedComponents - for (String component : mBaseThemeSupportedComponents) { - if (!mSelectedComponentsMap.containsKey(component)) return true; - } - return false; - } - - public void clearChanges() { - mSelectedComponentsMap.clear(); - mExternalWallpaperUri = null; - mExternalLockscreenUri = null; - View none = mLockScreenCard.findViewById(R.id.none); - if (none != null && none.getVisibility() == View.VISIBLE) { - none.setVisibility(View.GONE); - } - TextView tv = (TextView) mLockScreenCard.findViewById(R.id.label); - if (tv != null) { - tv.setAlpha(1f); - tv.setBackgroundResource(R.drawable.wallpaper_label_bg); - } - getLoaderManager().restartLoader(LOADER_ID_ALL, null, ThemeFragment.this); - } - - public String getThemePackageName() { - if (mPkgName == null) { - // check if the package name is defined in the arguments bundle - Bundle bundle = getArguments(); - if (bundle != null) { - mPkgName = bundle.getString(ARG_PACKAGE_NAME); - } - } - return mPkgName; - } - - private void uninstallTheme() { - getChooserActivity().uninstallTheme(mPkgName); - } - - public void setCurrentTheme(Map currentTheme, - MutableLong currentWallpaperComponentId) { - mCurrentTheme = currentTheme; - mCurrentWallpaperComponentId = currentWallpaperComponentId; - } - - /** - * Slides the scrollview content up and adds a space view at the bottom - * of mAdditionalCards so all content can be visible above the selector. - * - * We are using a ValueAnimator here to scroll the content rather than calling - * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. - * @param yDelta - * @param selectorHeight - */ - public void slideContentIntoView(final int yDelta, int selectorHeight) { - Space space = (Space) mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); - if (space == null) { - // No space view yet so lets create it one - space = new Space(getActivity()); - space.setId(ADDITIONAL_CONTENT_SPACE_ID); - mAdditionalCards.addView(space, - new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, - selectorHeight)); - } else { - // Space view already exists so just update the LayoutParams - LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) space.getLayoutParams(); - params.height = selectorHeight; - space.setLayoutParams(params); - } - final int startY = mScrollView.getScrollY(); - final ValueAnimator scrollAnimator = - ValueAnimator.ofInt(startY, startY + yDelta); - scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mScrollView.scrollTo(0, value); - } - }); - scrollAnimator.start(); - } - - public Map getSelectedComponentsMap() { - return mSelectedComponentsMap; - } - - /** - * Slides the scrollview content down and removes a space view at the bottom - * of mAdditionalCards. - * - * We are using a ValueAnimator here to scroll the content rather than calling - * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. - * @param yDelta - */ - public void slideContentBack(int yDelta) { - final int startY = mScrollView.getScrollY(); - final ValueAnimator scrollAnimator = - ValueAnimator.ofInt(startY, startY + yDelta); - scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mScrollView.scrollTo(0, value); - } - }); - scrollAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - View space = mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); - if (space != null) mAdditionalCards.removeView(space); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - scrollAnimator.start(); - } - - public void showLockScreenCard() { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - final int scrollToY = mStatusBarCard.getMeasuredHeight() - + mFontCard.getMeasuredHeight() + mIconCard.getMeasuredHeight() - + mNavBarCard.getMeasuredHeight() + mWallpaperCard.getMeasuredHeight() / 2; - final ValueAnimator scrollAnimator = ValueAnimator.ofInt(0, scrollToY); - scrollAnimator.setDuration(LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mScrollView.scrollTo(0, value); - } - }); - scrollAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mCardClickListener.onClick(mLockScreenCard); - } - }); - scrollAnimator.start(); - } - }, SHOW_LOCK_SCREEN_CARD_DELAY); - } - - protected void startLiveLockScreenSettings() { - Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - // TODO: inform user that this action failed (Toast?) - } - } - - class AnimationLoader extends AsyncTask { - Context mContext; - String mPkgName; - BootAniImageView mBootAnim; - - public AnimationLoader(Context context, String pkgName, BootAniImageView bootAnim) { - mContext = context; - mPkgName = pkgName; - mBootAnim = bootAnim; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected Boolean doInBackground(Void... params) { - if (mContext == null) { - return Boolean.FALSE; - } - ZipFile zip = null; - if (ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { - try { - zip = new ZipFile(new File(BootAnimationHelper.SYSTEM_BOOT_ANI_PATH)); - } catch (Exception e) { - Log.w(TAG, "Unable to load boot animation", e); - return Boolean.FALSE; - } - } else { - // check if the bootanimation is cached - File f = new File(mContext.getCacheDir(), - mPkgName + BootAnimationHelper.CACHED_SUFFIX); - if (!f.exists()) { - // go easy on cache storage and clear out any previous boot animations - BootAnimationHelper.clearBootAnimationCache(mContext); - try { - Context themeContext = mContext.createPackageContext(mPkgName, 0); - AssetManager am = themeContext.getAssets(); - InputStream is = am.open("bootanimation/bootanimation.zip"); - FileUtils.copyToFile(is, f); - is.close(); - } catch (Exception e) { - Log.w(TAG, "Unable to load boot animation", e); - return Boolean.FALSE; - } - } - try { - zip = new ZipFile(f); - } catch (IOException e) { - Log.w(TAG, "Unable to load boot animation", e); - return Boolean.FALSE; - } - } - if (zip != null) { - mBootAnim.setBootAnimation(zip); - } else { - return Boolean.FALSE; - } - return Boolean.TRUE; - } - - @Override - protected void onPostExecute(Boolean isSuccess) { - super.onPostExecute(isSuccess); - if (isSuccess) { - mBootAnim.start(); - } - } - } - - class AudibleLoadingThread extends Thread { - private Context mContext; - private int mType; - private String mPkgName; - private MediaPlayer mPlayer; - - public AudibleLoadingThread(Context context, int type, String pkgName, MediaPlayer mp) { - super(); - mContext = context; - mType = type; - mPkgName = pkgName; - mPlayer = mp; - } - - @Override - public void run() { - try { - AudioUtils.loadThemeAudible(mContext, mType, mPkgName, mPlayer); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to load sound for " + mPkgName, e); - } - } - } -} diff --git a/src/com/cyngn/theme/chooser/WallpaperCardView.java b/src/com/cyngn/theme/chooser/WallpaperCardView.java deleted file mode 100644 index 337af65..0000000 --- a/src/com/cyngn/theme/chooser/WallpaperCardView.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewOverlay; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -public class WallpaperCardView extends ComponentCardView { - protected ImageView mImage; - protected TextView mLabel; - - public WallpaperCardView(Context context) { - this(context, null); - } - - public WallpaperCardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WallpaperCardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WallpaperCardView); - String labelText = a.getString(R.styleable.WallpaperCardView_labelText); - a.recycle(); - - setOrientation(VERTICAL); - - setBackgroundResource(R.drawable.card_bg); - - LayoutInflater inflater = LayoutInflater.from(mContext); - FrameLayout frameLayout = - (FrameLayout) inflater.inflate(R.layout.wallpaper_card, this, false); - addView(frameLayout); - mLabel = (TextView) frameLayout.findViewById(R.id.label); - mImage = (ImageView) frameLayout.findViewById(R.id.image); - - mLabel.setText(labelText); - } - - public void setWallpaper(Drawable drawable) { - mImage.setImageDrawable(drawable); - View none = findViewById(R.id.none); - if (drawable == null) { - setBackgroundResource(R.drawable.card_wallpapertoggled_bg); - if (none != null) { - none.setVisibility(View.VISIBLE); - } - if (mLabel != null) { - mLabel.setBackgroundResource(0); - } - } else { - setBackgroundResource(0); - - if (none != null) { - none.setVisibility(View.GONE); - } - if (mLabel != null) { - mLabel.setBackgroundResource(R.drawable.wallpaper_label_bg); - } - } - } - - public void clearWallpaper() { - mImage.setImageDrawable(null); - setBackgroundResource(R.drawable.card_bg); - } - - public Drawable getWallpaperDrawable() { - return mImage.getDrawable(); - } - - @Override - public void expand(boolean showLabel) { - setEnabled(true); - } - - @Override - public void collapse() { - setEnabled(false); - } - - /** - * Animates a change in the content of the card - * @param v View in card to animate - * @param overlay Drawable to animate as a ViewOverlay - * @param duration Duration of animation - */ - @Override - public void animateContentChange(View v, final Drawable overlay, long duration) { - // Since the wallpaper IS the content, we will ignore the view passed in and animate - // the entire card - final ViewOverlay viewOverlay = this.getOverlay(); - viewOverlay.add(overlay); - final int x = 0; - final int y = 0; - final int width = v.getWidth(); - final int height = v.getHeight(); - overlay.setBounds(x, y, x + width, y + height); - - final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); - overlayAnimator.setDuration(duration); - overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (Float) animation.getAnimatedValue(); - overlay.setAlpha((int) (255 * value)); - } - - }); - overlayAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - // Clear out the ViewOverlay now that we are done animating - viewOverlay.clear(); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }); - - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(overlay, "alpha", 0f, 1f)) - .with(overlayAnimator); - set.start(); - } -} diff --git a/src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java b/src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java deleted file mode 100644 index e475351..0000000 --- a/src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.perapptheming; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.PointF; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Animation; -import android.widget.FrameLayout; - -import com.cyngn.theme.chooser.R; - -public class PerAppThemeListLayout extends FrameLayout { - private PerAppThemingWindow mWindow; - private PointF mCenter; - - private float mMaxRadius; - private float mTargetRadius; - private float mStartRadius; - private float mCurrentRadius; - - private ValueAnimator mAnimator; - private boolean mIsAnimating; - - private Path mRevealPath; - - public PerAppThemeListLayout(Context context) { - this(context, null); - } - - public PerAppThemeListLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PerAppThemeListLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - final Resources res = getResources(); - float width = res.getDimension(R.dimen.theme_list_width); - float height = res.getDimension(R.dimen.theme_list_max_height); - mMaxRadius = (float) Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); - mRevealPath = new Path(); - - mAnimator = new ValueAnimator(); - mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimator.addListener(mAnimationListener); - mAnimator.addUpdateListener(mUpdateListener); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN && - event.getKeyCode() == KeyEvent.KEYCODE_BACK && mWindow != null) { - mWindow.hideThemeList(); - } - return super.dispatchKeyEvent(event); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isEnabled() && event.getAction() == MotionEvent.ACTION_DOWN && mWindow != null) { - mWindow.hideThemeList(); - return true; - } - return super.onTouchEvent(event); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - if (!mIsAnimating) { - super.dispatchDraw(canvas); - } else { - final int state = canvas.save(); - mRevealPath.reset(); - mRevealPath.addCircle(mCenter.x, mCenter.y, mCurrentRadius, Path.Direction.CW); - canvas.clipPath(mRevealPath); - super.dispatchDraw(canvas); - canvas.restoreToCount(state); - } - } - - public void setPerAppThemingWindow(PerAppThemingWindow window) { - mWindow = window; - } - - /** - * Perform a circular reveal from center cx,cy - * @param cx X position of center - * @param cy Y position of center - * @param duration Duration of animation - */ - public void circularReveal(float cx, float cy, long duration) { - mCenter = new PointF(cx, cy); - mIsAnimating = true; - - mStartRadius = mCurrentRadius; - mTargetRadius = mMaxRadius; - startAnimation(duration); - } - - /** - * Perform a circular hide from center cx,cy - * @param cx X position of center - * @param cy Y position of center - * @param duration Duration of animation - */ - public void circularHide(float cx, float cy, long duration) { - mCenter = new PointF(cx, cy); - mIsAnimating = true; - - mStartRadius = mCurrentRadius; - mTargetRadius = 0f; - startAnimation(duration); - } - - private void startAnimation(long duration) { - getChildAt(0).setVisibility(View.VISIBLE); - mAnimator.setFloatValues(mStartRadius, mTargetRadius); - mAnimator.setDuration(duration); - mAnimator.start(); - } - - private ValueAnimator.AnimatorUpdateListener mUpdateListener = - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Float value = (Float) animation.getAnimatedValue(); - mCurrentRadius = value.floatValue(); - invalidate(); - } - }; - - private Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - mIsAnimating = false; - if (mCurrentRadius <= 0) { - getChildAt(0).setVisibility(INVISIBLE); - } - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }; -} diff --git a/src/com/cyngn/theme/perapptheming/PerAppThemeListView.java b/src/com/cyngn/theme/perapptheming/PerAppThemeListView.java deleted file mode 100644 index 5a4424f..0000000 --- a/src/com/cyngn/theme/perapptheming/PerAppThemeListView.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.perapptheming; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.ListView; -import com.cyngn.theme.chooser.R; -import com.cyngn.theme.util.Utils; - -public class PerAppThemeListView extends ListView { - private int mMinHeight; - private int mMaxHeight; - - public PerAppThemeListView(Context context) { - this(context, null); - } - - public PerAppThemeListView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PerAppThemeListView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - final Resources res = getResources(); - TypedArray a = context.obtainStyledAttributes(attrs, - Utils.getResourceDeclareStyleableIntArray("com.android.internal", "View")); - int resId = res.getIdentifier("View_minHeight", "styleable", "android"); - mMinHeight = a.getDimensionPixelSize(resId, 0); - a.recycle(); - - a = context.obtainStyledAttributes(attrs, R.styleable.PerAppThemeListView); - mMaxHeight = a.getDimensionPixelSize(R.styleable.PerAppThemeListView_maxHeight, 0); - a.recycle(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // let the super do the heavy lifting and then we'll cap the values to any max and/or min - // values that were defined in the layout - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int measuredWidth = getMeasuredWidth(); - int measuredHeight = getMeasuredHeight(); - int newHeight = measuredHeight; - if (mMaxHeight > 0) { - newHeight = Math.min(measuredHeight, mMaxHeight); - } - if (mMinHeight > 0) { - newHeight = Math.max(newHeight, mMinHeight); - } - if (newHeight != measuredHeight) { - setMeasuredDimension(measuredWidth, newHeight); - } - } -} diff --git a/src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java b/src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java deleted file mode 100644 index 4b6857b..0000000 --- a/src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.perapptheming; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.ContentObserver; -import android.database.Cursor; -import android.graphics.PixelFormat; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.WindowManager; -import android.view.animation.Interpolator; -import android.view.animation.OvershootInterpolator; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.cyngn.theme.chooser.R; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeChangeRequest; -import cyanogenmod.themes.ThemeManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -public class PerAppThemingWindow extends Service implements OnTouchListener, - ThemeManager.ThemeChangeListener { - // Animation frame rate per second - private static final int ANIMATION_FRAME_RATE = 60; - - private static final int EXIT_DELETE_MODE_ANIMATION_DURATION = 50; - - private static final int MOVE_TO_DELETE_BOX_ANIMATION_DURATION = 150; - - private static final int ANIMATION_DURATION = 300; - - private static final int FAB_SCALE_ANIMATION_DURATION = 150; - - private static final int LIST_ON_LEFT_SIDE = 0; - private static final int LIST_ON_RIGHT_SIDE = 1; - - // Don't want these colors to be themable and possibly alter the effect we are after, so - // they are defined here rather than in colors.xml - private static final int SCRIM_COLOR_TRANSPARENT = 0x00000000; - private static final int SCRIM_COLOR_OPAQUE = 0xaa000000; - - // Amount to wait after a theme change occurred before fading the scrim away - // This value was obtained empirically by performing theme changes and adjusting this delay - private static final int THEME_CHANGE_DELAY = 1500; - - private static final float PRESSED_FAB_SCALE = 0.95f; - - private static final float DELETE_BOX_ANIMATION_SCALE = 0.3f; - - private static final int MAX_DEPRECIATION = 5; - - private static final float FAB_ANIMATION_SCALE_FACTOR = 0.44f; - - // Margin around the phone - private static int MARGIN_VERTICAL; - // Margin around the phone - private static int MARGIN_HORIZONTAL; - private static int CLOSE_ANIMATION_DISTANCE; - private static int DRAG_DELTA; - private static int STARTING_POINT_Y; - private static int DELETE_BOX_WIDTH; - private static int DELETE_BOX_HEIGHT; - private static int FLOATING_WINDOW_ICON_SIZE; - - // View variables - private BroadcastReceiver mBroadcastReceiver; - private WindowManager mWindowManager; - private LinearLayout mDraggableIcon; - private View mDraggableIconImage; - private WindowManager.LayoutParams mParams; - private PerAppThemeListLayout mThemeListLayout; - private WindowManager.LayoutParams mListLayoutParams; - private ListView mThemeList; - private ThemesAdapter mAdapter; - private FrameLayout.LayoutParams mListParams; - private LinearLayout mDeleteView; - private View mDeleteBoxView; - private View mThemeApplyingView; - private boolean mDeleteBoxVisible = false; - private boolean mIsDestroyed = false; - private boolean mIsBeingDestroyed = false; - private int mCurrentPosX = -1; - - // Animation variables - private List mDeltaXArray; - private List mDeltaYArray; - private AnimationTask mAnimationTask; - - // Close logic - private int mCurrentX; - private int mCurrentY; - private boolean mIsInDeleteMode = false; - private boolean mIsAnimationLocked = false; - - // Drag variables - float mPrevDragX; - float mPrevDragY; - float mOrigX; - float mOrigY; - boolean mDragged; - - private int mListSide = LIST_ON_LEFT_SIDE; - - private ThemeConfig mThemeConfig; - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - - // Load margins, distances, etc. - final Resources res = getResources(); - MARGIN_VERTICAL = - res.getDimensionPixelSize(R.dimen.floating_window_margin_vertical); - MARGIN_HORIZONTAL = - res.getDimensionPixelSize(R.dimen.floating_window_margin_horizontal); - CLOSE_ANIMATION_DISTANCE = - res.getDimensionPixelSize(R.dimen.floating_window_close_animation_distance); - DRAG_DELTA = res.getDimensionPixelSize(R.dimen.floating_window_drag_delta); - STARTING_POINT_Y = res.getDimensionPixelSize(R.dimen.floating_window_starting_point_y); - - DELETE_BOX_WIDTH = (int) getResources().getDimension( - R.dimen.floating_window_delete_box_width); - DELETE_BOX_HEIGHT = (int) getResources().getDimension( - R.dimen.floating_window_delete_box_height); - FLOATING_WINDOW_ICON_SIZE = (int) getResources().getDimension( - R.dimen.floating_window_icon); - - mDeleteView = new LinearLayout(getContext()); - View.inflate(getContext(), R.layout.per_app_delete_box_window, mDeleteView); - mDeleteBoxView = mDeleteView.findViewById(R.id.box); - addView(mDeleteView, 0, 0, Gravity.BOTTOM | Gravity.CENTER_VERTICAL, - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.WRAP_CONTENT); - mDeleteView.setVisibility(View.GONE); - - mDraggableIcon = new LinearLayout(this); - mDraggableIcon.setOnTouchListener(this); - View.inflate(getContext(), R.layout.per_app_fab_floating_window_icon, mDraggableIcon); - mDraggableIconImage = mDraggableIcon.findViewById(R.id.box); - mDraggableIconImage.setClipToOutline(true); - mDraggableIconImage.getViewTreeObserver().addOnWindowAttachListener(mWindowAttachListener); - mParams = addView(mDraggableIcon, 0, 0); - updateIconPosition(MARGIN_HORIZONTAL, STARTING_POINT_Y); - - mThemeListLayout = (PerAppThemeListLayout) View.inflate(getContext(), - R.layout.per_app_theme_list, null); - mThemeListLayout.setPerAppThemingWindow(this); - mThemeList = (ListView) mThemeListLayout.findViewById(R.id.theme_list); - mListParams = (FrameLayout.LayoutParams) mThemeList.getLayoutParams(); - mThemeApplyingView = mThemeListLayout.findViewById(R.id.applying_theme_text); - - final Configuration config = getResources().getConfiguration(); - mThemeConfig = getThemeConfig(config); - loadThemes(); - getContentResolver().registerContentObserver(ThemesColumns.CONTENT_URI, true, - mThemesObserver); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mIsBeingDestroyed) return true; - - switch(event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (mThemeListLayout.isAttachedToWindow()) { - hideThemeList(); - return false; - } - mPrevDragX = mOrigX = event.getRawX(); - mPrevDragY = mOrigY = event.getRawY(); - - mDragged = false; - - mDeltaXArray = new LinkedList(); - mDeltaYArray = new LinkedList(); - - mCurrentX = mParams.x; - mCurrentY = mParams.y; - - mDraggableIconImage.setScaleX(PRESSED_FAB_SCALE); - mDraggableIconImage.setScaleY(PRESSED_FAB_SCALE); - - // Cancel any currently running animations - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - break; - case MotionEvent.ACTION_UP: - mIsAnimationLocked = false; - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - - if (!mDragged) { - // clicked so show theme list - final int mid = getScreenWidth() / 2; - mListSide = LIST_ON_LEFT_SIDE; - if (mCurrentPosX > mid) mListSide = LIST_ON_RIGHT_SIDE; - if (!mThemeListLayout.isAttachedToWindow()) showThemeList(); - } else { - // Animate the icon - mAnimationTask = new AnimationTask(); - mAnimationTask.run(); - } - - if (mIsInDeleteMode) { - close(true); - } else { - hideDeleteBox(); - mDraggableIconImage.setScaleX(1f); - mDraggableIconImage.setScaleY(1f); - } - break; - case MotionEvent.ACTION_MOVE: - mCurrentX = (int) (event.getRawX() - mDraggableIcon.getWidth() / 2); - mCurrentY = (int) (event.getRawY() - mDraggableIcon.getHeight()); - if (isDeleteMode()) { - mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); - mIsInDeleteMode = true; - updateIconPosition(mCurrentX, mCurrentY); - } else if (mIsInDeleteMode){ - mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_normal); - mIsInDeleteMode = false; - } else { - if(!mIsAnimationLocked && mDragged) { - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - updateIconPosition(mCurrentX, mCurrentY); - } - } - - float deltaX = event.getRawX() - mPrevDragX; - float deltaY = event.getRawY() - mPrevDragY; - - mDeltaXArray.add(deltaX); - mDeltaYArray.add(deltaY); - - mPrevDragX = event.getRawX(); - mPrevDragY = event.getRawY(); - - deltaX = event.getRawX() - mOrigX; - deltaY = event.getRawY() - mOrigY; - mDragged = mDragged || Math.abs(deltaX) > DRAG_DELTA - || Math.abs(deltaY) > DRAG_DELTA; - if (mDragged) { - showDeleteBox(); - } - break; - } - - return true; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mIsDestroyed = true; - if (mDraggableIcon != null) { - removeViewIfAttached(mDraggableIcon); - mDraggableIcon = null; - } - if (mDeleteView != null) { - removeViewIfAttached(mDeleteView); - mDeleteView = null; - } - if (mThemeListLayout != null) { - removeViewIfAttached(mThemeListLayout); - mThemeListLayout = null; - } - if (mAnimationTask != null) { - mAnimationTask.cancel(); - mAnimationTask = null; - } - if (mBroadcastReceiver != null) { - unregisterReceiver(mBroadcastReceiver); - mBroadcastReceiver = null; - } - if (mThemesObserver != null) { - getContentResolver().unregisterContentObserver(mThemesObserver); - mThemesObserver = null; - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mThemeConfig = getThemeConfig(newConfig); - } - - @Override - public void onProgress(int progress) { - } - - @Override - public void onFinish(boolean isSuccess) { - ThemeManager tm = ThemeManager.getInstance(getContext()); - tm.removeClient(this); - mThemeListLayout.postDelayed(new Runnable() { - @Override - public void run() { - hideScrim(); - startFabScaleUpAnimation(); - } - }, THEME_CHANGE_DELAY); - } - - public void hideThemeList() { - hideThemeList(false, new Runnable() { - @Override - public void run() { - removeViewIfAttached(mThemeListLayout); - } - }); - } - - private ThemeConfig getThemeConfig(Configuration config) { - if (config != null && config.themeConfig != null) { - return config.themeConfig; - } - - return ThemeConfig.getBootTheme(getContentResolver()); - } - - private void removeViewIfAttached(View view) { - if (view.isAttachedToWindow()) { - mWindowManager.removeViewImmediate(view); - } - } - - private WindowManager.LayoutParams addView(View v, int x, int y) { - return addView(v, x, y, Gravity.TOP | Gravity.LEFT, - WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); - } - - private WindowManager.LayoutParams addView(View v, int x, int y, int gravity, - int width, int height) { - mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); - WindowManager.LayoutParams params = new WindowManager.LayoutParams(width, height, - WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); - - params.gravity = gravity; - params.x = x; - params.y = y; - - mWindowManager.addView(v, params); - - return params; - } - - private void updateIconPosition(int x, int y) { - mCurrentPosX = x; - - View v = mDraggableIconImage; - v.setTranslationX(0); - if (x < 0) { - v.setTranslationX(x); - x = 0; - } - - if (x > getScreenWidth() - FLOATING_WINDOW_ICON_SIZE) { - v.setTranslationX(x - getScreenWidth() + FLOATING_WINDOW_ICON_SIZE); - x = getScreenWidth() - FLOATING_WINDOW_ICON_SIZE; - } - - v.setTranslationY(0); - if (y < 0) { - v.setTranslationY(y); - y = 0; - } - - if (y > getScreenHeight() - FLOATING_WINDOW_ICON_SIZE) { - v.setTranslationY(y - getScreenHeight() + FLOATING_WINDOW_ICON_SIZE); - y = getScreenHeight() - FLOATING_WINDOW_ICON_SIZE; - } - mParams.x = x; - mParams.y = y; - - if (!mIsDestroyed) { - mWindowManager.updateViewLayout(mDraggableIcon, mParams); - } - } - - private boolean isDeleteMode() { - return isHoveringOverDeleteBox(mParams.y); - } - - private boolean isHoveringOverDeleteBox(int y) { - return y + mDraggableIconImage.getHeight() >= getScreenHeight() - DELETE_BOX_HEIGHT; - } - - private void showDeleteBox() { - if (!mDeleteBoxVisible) { - mDeleteBoxVisible = true; - mDeleteView.setVisibility(View.VISIBLE); - - mDeleteBoxView.setAlpha(0); - mDeleteBoxView.setTranslationY(CLOSE_ANIMATION_DISTANCE); - mDeleteBoxView.animate().alpha(1).translationYBy(-1 * CLOSE_ANIMATION_DISTANCE) - .setListener(null); - - mDeleteBoxView.getLayoutParams().width = getScreenWidth(); - } - } - - private void hideDeleteBox() { - if (mDeleteBoxVisible) { - mDeleteBoxVisible = false; - if (mDeleteView != null) { - mDeleteBoxView.animate().alpha(0) - .translationYBy(CLOSE_ANIMATION_DISTANCE) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mDeleteView != null) mDeleteView.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } - } - } - - private void animateToDeleteBoxCenter(final OnAnimationFinishedListener l) { - if (mIsAnimationLocked) { - return; - } - mIsInDeleteMode = true; - - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - - mAnimationTask = new AnimationTask(getScreenWidth() / 2 - mDraggableIcon.getWidth() / 2, - getScreenHeight() - DELETE_BOX_HEIGHT / 2 - mDraggableIcon.getHeight() / 2); - mAnimationTask.setDuration(MOVE_TO_DELETE_BOX_ANIMATION_DURATION); - mAnimationTask.setAnimationFinishedListener(l); - mAnimationTask.run(); - mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); - } - - private void close(boolean animate) { - if (mIsBeingDestroyed) { - return; - } - mIsBeingDestroyed = true; - - if (animate) { - animateToDeleteBoxCenter(new OnAnimationFinishedListener() { - @Override - public void onAnimationFinished() { - hideDeleteBox(); - mDeleteBoxView.animate() - .scaleX(DELETE_BOX_ANIMATION_SCALE) - .scaleY(DELETE_BOX_ANIMATION_SCALE); - mDraggableIconImage.animate() - .scaleX(DELETE_BOX_ANIMATION_SCALE) - .scaleY(DELETE_BOX_ANIMATION_SCALE) - .translationY(CLOSE_ANIMATION_DISTANCE) - .setDuration(mDeleteBoxView.animate().getDuration()) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - stopSelf(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } - }); - } else { - stopSelf(); - } - } - - private static interface OnAnimationFinishedListener { - public void onAnimationFinished(); - } - - private Context getContext() { - return this; - } - - private int getScreenWidth() { - return getResources().getDisplayMetrics().widthPixels; - } - - private int getScreenHeight() { - return getResources().getDisplayMetrics().heightPixels - getStatusBarHeight(); - } - - private int getStatusBarHeight() { - int result = 0; - int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - result = getResources().getDimensionPixelSize(resourceId); - } - - return result; - } - - private void loadThemes() { - String[] columns = {ThemesColumns._ID, ThemesColumns.TITLE, ThemesColumns.PKG_NAME}; - String selection = ThemesColumns.MODIFIES_OVERLAYS + "=? AND " + - ThemesColumns.INSTALL_STATE + "=?"; - String[] selectionArgs = {"1", "" + ThemesColumns.InstallState.INSTALLED}; - String sortOrder = ThemesColumns.TITLE + " ASC"; - Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, columns, selection, - selectionArgs, sortOrder); - if (c != null) { - if (mAdapter == null) { - mAdapter = new ThemesAdapter(this, c); - mThemeList.setAdapter(mAdapter); - mThemeList.setOnItemClickListener(mThemeClickedListener); - } else { - String pkgName = (String) mAdapter.getItem(0); - mAdapter.populateThemes(c); - mAdapter.setCurrentTheme(pkgName); - } - } - } - - private ContentObserver mThemesObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - loadThemes(); - } - }; - - private ViewTreeObserver.OnWindowAttachListener mWindowAttachListener = - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - // Remove the this OnWindowAttachListener now that we are done with it. - mDraggableIconImage.getViewTreeObserver().removeOnWindowAttachListener(this); - - final float fabWidth = getResources().getDimension(R.dimen.floating_window_icon); - mDraggableIconImage.setAlpha(0); - mDraggableIconImage.setX(-fabWidth); - mDraggableIconImage.animate() - .alpha(1f) - .xBy(fabWidth) - .setDuration(ANIMATION_DURATION) - .start(); - } - - @Override - public void onWindowDetached() { - } - }; - - private void showThemeList() { - if (mListLayoutParams == null) { - mListLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_PHONE, - WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | - WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - } - mListLayoutParams.gravity = Gravity.TOP | - (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); - mWindowManager.addView(mThemeListLayout, mListLayoutParams); - - setThemeListPosition(); - startFabScaleDownAnimation(); - - mAdapter.setCurrentTheme( - mThemeConfig.getOverlayPkgNameForApp(Utils.getTopTaskPackageName(this))); - mThemeListLayout.circularReveal(mParams.x + mDraggableIconImage.getWidth() / 2, - mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); - } - - private void hideThemeList(boolean showScrim, final Runnable endAction) { - if (showScrim) { - showScrim(); - } else { - startFabScaleUpAnimation(); - } - mThemeListLayout.circularHide(mParams.x + mDraggableIconImage.getWidth() / 2, - mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); - if (endAction != null) { - mDraggableIcon.postDelayed(endAction, ANIMATION_DURATION); - } - } - - private void showScrim() { - ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_TRANSPARENT, - SCRIM_COLOR_OPAQUE); - mThemeListLayout.setEnabled(false); - animator.setDuration(ANIMATION_DURATION) - .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Integer value = (Integer) animation.getAnimatedValue(); - mThemeListLayout.setBackgroundColor(value.intValue()); - } - }); - animator.start(); - mThemeApplyingView.animate() - .alpha(1f) - .setDuration(ANIMATION_DURATION); - } - - private void hideScrim() { - ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_OPAQUE, SCRIM_COLOR_TRANSPARENT); - animator.setDuration(ANIMATION_DURATION) - .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Integer value = (Integer) animation.getAnimatedValue(); - mThemeListLayout.setBackgroundColor(value.intValue()); - } - }); - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - removeViewIfAttached(mThemeListLayout); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - animator.start(); - mThemeApplyingView.animate() - .alpha(0f) - .setDuration(ANIMATION_DURATION); - mDraggableIcon.setVisibility(View.VISIBLE); - mDraggableIconImage.animate() - .alpha(1f) - .setDuration(ANIMATION_DURATION); - } - - private void setThemeListPosition() { - int thirdHeight = getScreenHeight() / 3; - // use the center of the fab to decide where to place the list - int fabLocationY = mParams.y + mDraggableIconImage.getHeight() / 2; - int listHeight = mThemeList.getMeasuredHeight(); - if (listHeight <= 0) { - // not measured yet so let's force that - int width = getResources().getDimensionPixelSize(R.dimen.theme_list_width); - int height = getResources().getDimensionPixelSize(R.dimen.theme_list_max_height); - mThemeList.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)); - listHeight = mThemeList.getMeasuredHeight(); - } - - // If we're in the top 1/3 of the screen position the top of the list with the top - // of the fab. Second 3rd will position the list so that it is vertically centered - // with the fab center. Bottom 3rd will position the bottom of the list with the - // bottom of the fab. - if (fabLocationY < thirdHeight) { - mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2; - } else if (fabLocationY < thirdHeight * 2) { - mListParams.topMargin = fabLocationY - listHeight / 2; - } else { - mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2 - listHeight; - } - mListParams.gravity = Gravity.TOP | - (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); - mThemeList.setLayoutParams(mListParams); - } - - private void startFabScaleDownAnimation() { - final int iconWidth = mDraggableIconImage.getWidth(); - final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * - (mListSide == LIST_ON_LEFT_SIDE ? -1 : 1); - - mDraggableIconImage.animate() - .scaleX(FAB_ANIMATION_SCALE_FACTOR) - .scaleY(FAB_ANIMATION_SCALE_FACTOR) - .translationXBy(translateX) - .setDuration(FAB_SCALE_ANIMATION_DURATION); - } - - private void startFabScaleUpAnimation() { - final float iconWidth = mDraggableIconImage.getWidth(); - final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * - (mListSide == LIST_ON_LEFT_SIDE ? 1 : -1); - - mDraggableIconImage.animate() - .scaleX(1f) - .scaleY(1f) - .translationXBy(translateX) - .setDuration(FAB_SCALE_ANIMATION_DURATION); - } - - private AdapterView.OnItemClickListener mThemeClickedListener = - new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - final String themePkgName = (String) view.getTag(R.id.tag_key_name); - final String appPkgName = Utils.getTopTaskPackageName(getContext()); - if (!TextUtils.isEmpty(appPkgName) && !TextUtils.isEmpty(themePkgName)) { - if (!Utils.themeHasOverlayForApp(getContext(), appPkgName, themePkgName)) { - Toast.makeText(getContext(), R.string.per_app_theme_app_not_overlaid_warning, - Toast.LENGTH_LONG).show(); - } - hideThemeList(true, new Runnable() { - @Override - public void run() { - ThemeManager tm = ThemeManager.getInstance(getContext()); - ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); - builder.setAppOverlay(appPkgName, themePkgName); - try { - tm.addClient(PerAppThemingWindow.this); - } catch (IllegalArgumentException e) { - /* ignore since this means we already have a listener added */ - } - tm.requestThemeChange(builder.build(), false); - } - }); - } else { - hideThemeList(); - } - } - }; - - private float calculateVelocityX() { - int depreciation = mDeltaXArray.size() + 1; - float sum = 0; - for (Float f : mDeltaXArray) { - depreciation--; - if (depreciation > MAX_DEPRECIATION){ - continue; - } - - sum += f / depreciation; - } - - return sum; - } - - private float calculateVelocityY() { - int depreciation = mDeltaYArray.size() + 1; - float sum = 0; - for (Float f : mDeltaYArray) { - depreciation--; - if (depreciation > 5) { - continue; - } - - sum += f / depreciation; - } - - return sum; - } - - // Timer for animation/automatic movement of the tray - private class AnimationTask { - // Ultimate destination coordinates toward which the view will move - int mDestX; - int mDestY; - long mDuration = 350; - long mStartTime; - float mTension = 1.4f; - Interpolator mInterpolator = new OvershootInterpolator(mTension); - long mSteps; - long mCurrentStep; - int mDistX; - int mOrigX; - int mDistY; - int mOrigY; - Handler mAnimationHandler = new Handler(); - OnAnimationFinishedListener mAnimationFinishedListener; - - public AnimationTask(int x, int y) { - setup(x, y); - } - - public AnimationTask() { - setup(calculateX(), calculateY()); - - float velocityX = calculateVelocityX(); - float velocityY = calculateVelocityY(); - mTension += Math.sqrt(velocityX * velocityX + velocityY * velocityY) / 200; - mInterpolator = new OvershootInterpolator(mTension); - } - - private void setup(int x, int y) { - if (mIsAnimationLocked) { - throw new RuntimeException("Returning to user's finger. Avoid animations while " + - "mIsAnimationLocked flag is set."); - } - - mDestX = x; - mDestY = y; - - mSteps = (int) (((float) mDuration) / 1000 * ANIMATION_FRAME_RATE); - mCurrentStep = 1; - mDistX = mParams.x - mDestX; - mOrigX = mParams.x; - mDistY = mParams.y - mDestY; - mOrigY = mParams.y; - } - - public long getDuration() { - return mDuration; - } - - public void setDuration(long duration) { - mDuration = duration; - setup(mDestX, mDestY); - } - - public OnAnimationFinishedListener getAnimationFinishedListener() { - return mAnimationFinishedListener; - } - - public void setAnimationFinishedListener(OnAnimationFinishedListener l) { - mAnimationFinishedListener = l; - } - - public Interpolator getInterpolator() { - return mInterpolator; - } - - public void setInterpolator(Interpolator interpolator) { - mInterpolator = interpolator; - } - - private int calculateX() { - float velocityX = calculateVelocityX(); - int screenWidth = getScreenWidth(); - int destX = (mParams.x + mDraggableIcon.getWidth() / 2 > screenWidth / 2) - ? screenWidth - mDraggableIcon.getWidth() - MARGIN_HORIZONTAL - : 0 + MARGIN_HORIZONTAL; - - if (Math.abs(velocityX) > 50) { - destX = (velocityX > 0) ? screenWidth - mDraggableIcon.getWidth() - - MARGIN_HORIZONTAL : 0 + MARGIN_HORIZONTAL; - } - - return destX; - } - - private int calculateY() { - float velocityY = calculateVelocityY(); - mInterpolator = new OvershootInterpolator(mTension); - int screenHeight = getScreenHeight(); - int destY = mParams.y + (int) (velocityY * 3); - if (destY <= 0) { - destY = MARGIN_VERTICAL; - } - if (destY >= screenHeight - mDraggableIcon.getHeight()) { - destY = screenHeight - mDraggableIcon.getHeight() - MARGIN_VERTICAL; - } - - return destY; - } - - public void run() { - mStartTime = System.currentTimeMillis(); - for (mCurrentStep = 1; mCurrentStep <= mSteps; mCurrentStep++) { - long delay = mCurrentStep * mDuration / mSteps; - final float currentStep = mCurrentStep; - mAnimationHandler.postDelayed(new Runnable() { - @Override - public void run() { - // Update coordinates of the view - float percent = mInterpolator.getInterpolation(currentStep / mSteps); - updateIconPosition(mOrigX - (int) (percent * mDistX), mOrigY - - (int) (percent * mDistY)); - - // Notify the animation has ended - if (currentStep >= mSteps) { - if (mAnimationFinishedListener != null) mAnimationFinishedListener - .onAnimationFinished(); - } - } - }, delay); - } - } - - public void cancel() { - mAnimationHandler.removeCallbacksAndMessages(null); - mAnimationTask = null; - } - } - - /** - * We're extending BaseAdapter rather than CursorAdapter so that we can quickly re-order - * the list without needing to requery the provider. We're only storing the package name - * and theme title so there is minimum memory impact on doing this. - */ - class ThemesAdapter extends BaseAdapter { - private static final float HALF_OPACITY = 0.5f; - private static final float FULL_OPACITY = 1.0f; - - private ArrayList mThemes; - private LayoutInflater mInflater; - - public ThemesAdapter(Context context, Cursor cursor) { - mInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE); - mThemes = new ArrayList(cursor.getCount()); - populateThemes(cursor); - cursor.close(); - } - - @Override - public int getCount() { - return mThemes.size(); - } - - @Override - public Object getItem(int position) { - return mThemes.get(position).pkgName; - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = mInflater.inflate(R.layout.per_app_theme_list_item, parent, false); - Holder holder = new Holder(); - holder.title = (TextView) convertView.findViewById(R.id.theme_title); - holder.indicator = (TextView) convertView.findViewById(R.id.selected_indicator); - convertView.setTag(R.id.tag_key_holder, holder); - } - ThemeInfo themeInfo = mThemes.get(position); - Holder holder = (Holder) convertView.getTag(R.id.tag_key_holder); - holder.title.setText(themeInfo.title); - if (position == 0) { - holder.title.setAlpha(HALF_OPACITY); - holder.indicator.setVisibility(View.VISIBLE); - convertView.setEnabled(false); - } else { - holder.title.setAlpha(FULL_OPACITY); - holder.indicator.setVisibility(View.INVISIBLE); - convertView.setEnabled(true); - } - convertView.setTag(R.id.tag_key_name, themeInfo.pkgName); - return convertView; - } - - @Override - public boolean isEnabled(int position) { - return position != 0; - } - - public void setCurrentTheme(String pkgName) { - ThemeInfo info = null; - for (ThemeInfo ti : mThemes) { - if (ti.pkgName.equals(pkgName)) { - info = ti; - break; - } - } - if (info != null) { - Collections.sort(mThemes); - mThemes.remove(info); - mThemes.add(0, info); - notifyDataSetChanged(); - } - } - - private void populateThemes(Cursor cursor) { - mThemes.clear(); - while(cursor.moveToNext()) { - ThemeInfo info = new ThemeInfo( - cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)), - cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); - mThemes.add(info); - } - } - - private class Holder { - TextView title; - TextView indicator; - } - - private class ThemeInfo implements Comparable { - String pkgName; - String title; - - public ThemeInfo(String pkgName, String title) { - this.pkgName = pkgName; - this.title = title; - } - - @Override - public int compareTo(Object another) { - return this.title.compareTo(((ThemeInfo)another).title); - } - } - } -} diff --git a/src/com/cyngn/theme/util/AudioUtils.java b/src/com/cyngn/theme/util/AudioUtils.java deleted file mode 100644 index 62076db..0000000 --- a/src/com/cyngn/theme/util/AudioUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.content.res.ThemeConfig; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.util.Log; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.IOException; - -public class AudioUtils { - private static final String TAG = AudioUtils.class.getSimpleName(); - - public static void loadThemeAudible(Context context, int type, String pkgName, MediaPlayer mp) - throws PackageManager.NameNotFoundException { - if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) { - loadSystemAudible(type, mp); - return; - } - PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); - Context themeCtx = context.createPackageContext(pkgName, 0); - AssetManager assetManager = themeCtx.getAssets(); - String assetPath; - switch (type) { - case RingtoneManager.TYPE_ALARM: - assetPath = "alarms"; - break; - case RingtoneManager.TYPE_NOTIFICATION: - assetPath = "notifications"; - break; - case RingtoneManager.TYPE_RINGTONE: - assetPath = "ringtones"; - break; - default: - assetPath = null; - break; - } - if (assetPath != null) { - try { - String[] assetList = assetManager.list(assetPath); - if (assetList != null && assetList.length > 0) { - AssetFileDescriptor afd = assetManager.openFd(assetPath - + File.separator + assetList[0]); - if (mp != null) { - mp.reset(); - mp.setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), afd.getLength()); - mp.prepare(); - } - } - } catch (IOException e) { - Log.e(TAG, "Unable to load sound for " + pkgName, e); - } - } - } - - public static void loadSystemAudible(int type, MediaPlayer mp) { - final String audiblePath = ThemeUtils.getDefaultAudiblePath(type); - if (audiblePath != null && (new File(audiblePath)).exists()) { - try { - mp.reset(); - mp.setDataSource(audiblePath); - mp.prepare(); - } catch (IOException e) { - Log.e(TAG, "Unable to load system sound " + audiblePath, e); - } - } - } - - public static Uri loadDefaultAudible(Context context, int type, MediaPlayer mp) - throws IOException { - Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); - if (ringtoneUri != null) { - mp.reset(); - mp.setDataSource(context, ringtoneUri); - mp.prepare(); - } - - return ringtoneUri; - } -} diff --git a/src/com/cyngn/theme/util/BootAnimationHelper.java b/src/com/cyngn/theme/util/BootAnimationHelper.java deleted file mode 100644 index 283f854..0000000 --- a/src/com/cyngn/theme/util/BootAnimationHelper.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.res.ThemeConfig; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.AsyncTask; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; - -public class BootAnimationHelper { - private static final String TAG = BootAnimationHelper.class.getSimpleName(); - private static final int MAX_REPEAT_COUNT = 3; - - public static final String THEME_INTERNAL_BOOT_ANI_PATH = - "assets/bootanimation/bootanimation.zip"; - public static final String SYSTEM_BOOT_ANI_PATH = "/system/media/bootanimation.zip"; - public static final String CACHED_SUFFIX = "_bootanimation.zip"; - - public static final int NUM_FIRST_LINE_PARAMETERS = 3; - public static final int NUM_PART_LINE_PARAMETERS = 4; - - public static class AnimationPart { - /** - * Number of times to play this part - */ - public int playCount; - /** - * If non-zero, pause for the given # of seconds before moving on to next part. - */ - public int pause; - /** - * The name of this part - */ - public String partName; - /** - * Time each frame is displayed - */ - public int frameRateMillis; - /** - * List of file names for the given frames in this part - */ - public List frames; - /** - * width of the animation - */ - public int width; - /** - * height of the animation - */ - public int height; - - public AnimationPart(int playCount, int pause, String partName, int frameRateMillis, - int width, int height) { - this.playCount = playCount == 0 ? MAX_REPEAT_COUNT : playCount; - this.pause = pause; - this.partName = partName; - this.frameRateMillis = frameRateMillis; - this.width = width; - this.height = height; - frames = new ArrayList(); - } - - public void addFrame(String frame) { - frames.add(frame); - } - } - - /** - * Gather up all the details for the given boot animation - * @param zip The bootanimation.zip - * @return A list of AnimationPart if successful, null if not. - * @throws IOException - */ - public static List parseAnimation(ZipFile zip) - throws IOException, BootAnimationException { - if (zip == null) { - // To make tracking down boot animation problems we'll throw a BootAnimationException - // instead of an IllegalArgumentException. - throw new BootAnimationException("Boot animation ZipFile cannot be null"); - } - List animationParts = null; - - ZipEntry ze = zip.getEntry("desc.txt"); - if (ze != null) { - animationParts = parseDescription(zip.getInputStream(ze)); - } else { - throw new BootAnimationException("Missing desc.txt in root of bootanimation.zip"); - } - - if (animationParts == null) { - // We really should not end up here but in case we do here's an exception for ya! - throw new BootAnimationException("Unable to load boot animation."); - } - - Iterator iterator = animationParts.iterator(); - while(iterator.hasNext()) { - AnimationPart a = iterator.next(); - for (Enumeration e = zip.entries();e.hasMoreElements();) { - ze = e.nextElement(); - if (!ze.isDirectory() && ze.getName().contains(a.partName)) { - a.addFrame(ze.getName()); - } - } - if (a.frames.size() > 0) { - Collections.sort(a.frames); - } else { - // This boot animation may be salvageable if there are still some other parts - // that are good. We'll remove this part and if there are no parts left by - // the time we have iterated over all the parts then we can throw an exception. - Log.w(TAG, String.format("No frames in part %s, removing from animation", - a.partName)); - iterator.remove(); - } - } - if (animationParts.size() == 0) { - throw new BootAnimationException("Boot animation must have at least one part."); - } - - return animationParts; - } - - /** - * Parses the desc.txt of the boot animation - * @param in InputStream to the desc.txt - * @return A list of the parts as given in desc.txt - * @throws IOException - */ - private static List parseDescription(InputStream in) - throws IOException, BootAnimationException { - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - - // read in suggested width, height, and frame rate from first line - String line = reader.readLine(); - String[] details = line.split(" "); - if (details.length != NUM_FIRST_LINE_PARAMETERS) { - throw new BootAnimationException(String.format( - "Invalid # of parameters on first line of desc.txt; exptected %d, read %d " + - "(\"%s\")", - NUM_FIRST_LINE_PARAMETERS, details.length, line)); - } - - // The items should be in the following order: width, height, frame rate - final int width = Integer.parseInt(details[0]); - final int height = Integer.parseInt(details[1]); - final int frameRateMillis = 1000 / Integer.parseInt(details[2]); - - List animationParts = new ArrayList(); - while ((line = reader.readLine()) != null) { - // trim off any leading and trailing spaces - line = line.trim(); - // if the line is empty continue on to the next - if (TextUtils.isEmpty(line)) continue; - - String[] info = line. split(" "); - if (info.length != NUM_PART_LINE_PARAMETERS) { - Log.w(TAG, String.format( - "Invalid # of part parameters; exptected %d, read %d (\"%s\")", - NUM_PART_LINE_PARAMETERS, info.length, line)); - // let's continue in case there are parts that are valid - continue; - } - if (!info[0].equals("p") && !info[0].equals("c")) { - Log.w(TAG, String.format( - "Unknown part type; expected 'p' or 'c', read %s (\"%s\")", info[0], line)); - - // let's continue in case there are parts that are valid - continue; - } - int playCount = Integer.parseInt(info[1]); - int pause = Integer.parseInt(info[2]); - String name = info[3]; - AnimationPart ap = new AnimationPart(playCount, pause, name, frameRateMillis, - width, height); - animationParts.add(ap); - } - - return animationParts; - } - - public static String getPreviewFrameEntryName(InputStream is) throws IOException { - ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is - : new ZipInputStream(new BufferedInputStream(is)); - ZipEntry ze; - // First thing to do is iterate over all the entries and the zip and store them - // for building the animations afterwards - String previewName = null; - while ((ze = zis.getNextEntry()) != null) { - final String entryName = ze.getName(); - if (entryName.contains("/") - && (entryName.endsWith(".png") || entryName.endsWith(".jpg"))) { - previewName = entryName; - } - } - - return previewName; - } - - public static Bitmap loadPreviewFrame(Context context, InputStream is, String previewName) - throws IOException { - ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is - : new ZipInputStream(new BufferedInputStream(is)); - ZipEntry ze; - ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inSampleSize = am.isLowRamDevice() ? 4 : 2; - opts.inPreferredConfig = Bitmap.Config.RGB_565; - // First thing to do is iterate over all the entries and the zip and store them - // for building the animations afterwards - Bitmap preview = null; - while ((ze = zis.getNextEntry()) != null && preview == null) { - final String entryName = ze.getName(); - if (entryName.equals(previewName)) { - preview = BitmapFactory.decodeStream(zis, null, opts); - } - } - zis.close(); - - return preview; - } - - public static void clearBootAnimationCache(Context context) { - File cache = context.getCacheDir(); - if (cache.exists()) { - for(File f : cache.listFiles()) { - // volley stores stuff in cache so don't delete the volley directory - if(!f.isDirectory() && f.getName().endsWith(CACHED_SUFFIX)) f.delete(); - } - } - } - - public static class LoadBootAnimationImage extends AsyncTask { - private ImageView imv; - private String path; - private Context context; - - public LoadBootAnimationImage(ImageView imv, Context context, String path) { - this.imv = imv; - this.context = context; - this.path = path; - } - - @Override - protected Bitmap doInBackground(Object... params) { - Bitmap bitmap = null; - String previewName = null; - // this is ugly, ugly, ugly. Did I mention this is ugly? - try { - if (ThemeConfig.SYSTEM_DEFAULT.equals(path)) { - previewName = getPreviewFrameEntryName( - new FileInputStream(SYSTEM_BOOT_ANI_PATH)); - bitmap = loadPreviewFrame( - context, new FileInputStream(SYSTEM_BOOT_ANI_PATH), previewName); - } else { - final Context themeCtx = context.createPackageContext(path, 0); - previewName = getPreviewFrameEntryName( - themeCtx.getAssets().open("bootanimation/bootanimation.zip")); - bitmap = loadPreviewFrame(context, - themeCtx.getAssets().open("bootanimation/bootanimation.zip"), - previewName); - } - } catch (Exception e) { - // don't care since a null bitmap will be returned - e.printStackTrace(); - } - return bitmap; - } - - @Override - protected void onPostExecute(Bitmap result) { - if (result != null && imv != null) { - imv.setVisibility(View.VISIBLE); - imv.setImageBitmap(result); - } - } - } - - public static class BootAnimationException extends Exception { - public BootAnimationException(String detailMessage) { - super(detailMessage); - } - } -} diff --git a/src/com/cyngn/theme/util/CursorLoaderHelper.java b/src/com/cyngn/theme/util/CursorLoaderHelper.java deleted file mode 100644 index 5992779..0000000 --- a/src/com/cyngn/theme/util/CursorLoaderHelper.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; - -import cyanogenmod.app.ThemeVersion; -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; - -public class CursorLoaderHelper { - - public static final int LOADER_ID_INVALID = -1; - public static final int LOADER_ID_ALL = 0; - public static final int LOADER_ID_STATUS_BAR = 1; - public static final int LOADER_ID_FONT = 2; - public static final int LOADER_ID_ICONS = 3; - public static final int LOADER_ID_WALLPAPER = 4; - public static final int LOADER_ID_NAVIGATION_BAR = 5; - public static final int LOADER_ID_LOCKSCREEN = 6; - public static final int LOADER_ID_STYLE = 7; - public static final int LOADER_ID_BOOT_ANIMATION = 8; - public static final int LOADER_ID_RINGTONE = 9; - public static final int LOADER_ID_NOTIFICATION = 10; - public static final int LOADER_ID_ALARM = 11; - public static final int LOADER_ID_LIVE_LOCK_SCREEN = 12; - public static final int LOADER_ID_INSTALLED_THEMES = 1000; - public static final int LOADER_ID_APPLIED = 1001; - - private static final long DEFAULT_COMPONENT_ID = 0; - - private static int mThemeVersion = ThemeVersion.getVersion(); - - public static Loader chooserActivityCursorLoader(Context context, int id, - String appliedBaseTheme) { - String selection = null; - String selectionArgs[] = null; - String sortOrder = null; - String[] projection = null; - Uri contentUri = null; - - switch (id) { - case LOADER_ID_INSTALLED_THEMES: - selection = ThemesColumns.PRESENT_AS_THEME + "=? AND " + - ThemesColumns.INSTALL_STATE + "=?"; - selectionArgs = new String[] { "1", "" + ThemesColumns.InstallState.INSTALLED}; - // sort in ascending order but make sure the "default" theme is always first - sortOrder = "(" + ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " - + "(" + ThemesColumns.PKG_NAME + "='" + appliedBaseTheme + "') DESC, " - + ThemesColumns.INSTALL_TIME + " DESC"; - contentUri = ThemesColumns.CONTENT_URI; - projection = new String[] {ThemesColumns.PKG_NAME, ThemesColumns.TITLE, - ThemesColumns.AUTHOR}; - break; - case LOADER_ID_APPLIED: - //TODO: Mix n match query should only be done once - contentUri = ThemesContract.MixnMatchColumns.CONTENT_URI; - selection = null; - selectionArgs = null; - break; - } - - return new CursorLoader(context, contentUri, projection, selection, - selectionArgs, sortOrder); - } - - public static Loader componentSelectorCursorLoader(Context context, int id) { - Uri uri = PreviewColumns.CONTENT_URI; - String selection; - String[] selectionArgs = { "1" }; - String[] projection = { ThemesColumns.TITLE, ThemesColumns.PKG_NAME }; - switch(id) { - case LOADER_ID_STATUS_BAR: - selection = MODIFIES_STATUS_BAR + "=?"; - projection = new String[] { - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_NAVIGATION_BAR: - selection = MODIFIES_NAVIGATION_BAR + "=?"; - projection = new String[] { - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.STATUSBAR_BACKGROUND, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - }; - break; - case LOADER_ID_FONT: - // fonts don't have generated previews so use the ThemesColumns.CONTENT_URI - uri = ThemesColumns.CONTENT_URI; - selection = MODIFIES_FONTS + "=?"; - break; - case LOADER_ID_ICONS: - selection = MODIFIES_ICONS + "=?"; - projection = new String[] { - PreviewColumns.ICON_PREVIEW_1, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_STYLE: - selection = MODIFIES_OVERLAYS + "=?"; - projection = new String[] { - PreviewColumns.STYLE_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_WALLPAPER: - selection = MODIFIES_LAUNCHER + "=?"; - if (mThemeVersion >= 3) { - uri = PreviewColumns.COMPONENTS_URI; - projection = new String[]{ - PreviewColumns.WALLPAPER_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - PreviewColumns.WALLPAPER_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - } - break; - case LOADER_ID_BOOT_ANIMATION: - selection = MODIFIES_BOOT_ANIM + "=?"; - projection = new String[] { - PreviewColumns.BOOTANIMATION_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_RINGTONE: - selection = MODIFIES_RINGTONES + "=?"; - break; - case LOADER_ID_NOTIFICATION: - selection = MODIFIES_NOTIFICATIONS + "=?"; - break; - case LOADER_ID_ALARM: - selection = MODIFIES_ALARMS + "=?"; - break; - case LOADER_ID_LOCKSCREEN: - selection = MODIFIES_LOCKSCREEN + "=?"; - selectionArgs = new String[] { "1" }; - if (mThemeVersion >= 3) { - projection = new String[]{ - PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - ThemesColumns.PKG_NAME - }; - } - break; - case LOADER_ID_LIVE_LOCK_SCREEN: - selection = MODIFIES_LIVE_LOCK_SCREEN + "=?"; - selectionArgs = new String[] { "1" }; - if (mThemeVersion >= 3) { - projection = new String[]{ - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - ThemesColumns.PKG_NAME - }; - } - break; - default: - return null; - } - // sort in ascending order but make sure the "default" theme is always first - String sortOrder = "(" + ThemesContract.ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " - + ThemesContract.ThemesColumns.TITLE + " ASC"; - return new CursorLoader(context, uri, projection, selection, selectionArgs, sortOrder); - } - - public static Loader myThemeFragmentCursorLoader(Context context, int id) { - Uri uri; - String[] projection; - projection = new String[]{ - PreviewColumns.WALLPAPER_PREVIEW, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.NAVBAR_HOME_BUTTON, - PreviewColumns.NAVBAR_RECENT_BUTTON, - PreviewColumns.ICON_PREVIEW_1, - PreviewColumns.ICON_PREVIEW_2, - PreviewColumns.ICON_PREVIEW_3, - PreviewColumns.LOCK_WALLPAPER_PREVIEW, - PreviewColumns.STYLE_PREVIEW, - PreviewColumns.NAVBAR_BACKGROUND, - PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW - }; - uri = PreviewColumns.APPLIED_URI; - return new CursorLoader(context, uri, projection, null, null, null); - } - - public static Loader themeFragmentCursorLoader(Context context, int id, String pkgName, - long componentId) { - Uri uri = PreviewColumns.CONTENT_URI; - String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?"; - String[] selectionArgs = new String[] { pkgName }; - String[] projection = null; - switch (id) { - case LOADER_ID_ALL: - if (mThemeVersion >= 3) { - // Load all default component previews (component_id == 0) - selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; - selectionArgs = new String[] { pkgName, String.valueOf(DEFAULT_COMPONENT_ID) }; - } else { - // SQL query will fail if we ask for PreviewColumns.COMPONENT_ID, don't add it. - selectionArgs = new String[]{pkgName}; - } - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - ThemesColumns.AUTHOR, - ThemesColumns.WALLPAPER_URI, - ThemesColumns.HOMESCREEN_URI, - ThemesColumns.TARGET_API, - // Theme abilities - ThemesColumns.MODIFIES_LAUNCHER, - ThemesColumns.MODIFIES_LOCKSCREEN, - ThemesColumns.MODIFIES_ALARMS, - ThemesColumns.MODIFIES_BOOT_ANIM, - ThemesColumns.MODIFIES_FONTS, - ThemesColumns.MODIFIES_ICONS, - ThemesColumns.MODIFIES_NAVIGATION_BAR, - ThemesColumns.MODIFIES_OVERLAYS, - ThemesColumns.MODIFIES_RINGTONES, - ThemesColumns.MODIFIES_STATUS_BAR, - ThemesColumns.MODIFIES_NOTIFICATIONS, - //Previews - PreviewColumns.WALLPAPER_PREVIEW, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.NAVBAR_HOME_BUTTON, - PreviewColumns.NAVBAR_RECENT_BUTTON, - PreviewColumns.ICON_PREVIEW_1, - PreviewColumns.ICON_PREVIEW_2, - PreviewColumns.ICON_PREVIEW_3, - PreviewColumns.LOCK_WALLPAPER_PREVIEW, - PreviewColumns.STYLE_PREVIEW, - PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW - }; - break; - case LOADER_ID_STATUS_BAR: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT - }; - break; - case LOADER_ID_FONT: - uri = ThemesColumns.CONTENT_URI; - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE - }; - break; - case LOADER_ID_ICONS: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.ICON_PREVIEW_1, - PreviewColumns.ICON_PREVIEW_2, - PreviewColumns.ICON_PREVIEW_3, - }; - break; - case LOADER_ID_WALLPAPER: - if (mThemeVersion >= 3) { - uri = PreviewColumns.COMPONENTS_URI; - // Load specified wallpaper previews (component_id is specified) - selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; - selectionArgs = new String[]{pkgName, String.valueOf(componentId)}; - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.WALLPAPER_PREVIEW, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.WALLPAPER_PREVIEW - }; - } - break; - case LOADER_ID_NAVIGATION_BAR: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.NAVBAR_HOME_BUTTON, - PreviewColumns.NAVBAR_RECENT_BUTTON - }; - break; - case LOADER_ID_LOCKSCREEN: - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.LOCK_WALLPAPER_PREVIEW, - }; - break; - case LOADER_ID_LIVE_LOCK_SCREEN: - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW - }; - break; - case LOADER_ID_STYLE: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.STYLE_PREVIEW - }; - break; - case LOADER_ID_BOOT_ANIMATION: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE - }; - break; - case LOADER_ID_RINGTONE: - case LOADER_ID_NOTIFICATION: - case LOADER_ID_ALARM: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE - }; - break; - } - return new CursorLoader(context, uri, projection, selection, selectionArgs, null); - } - - public static Object[] getRowFromCursor(Cursor cursor) { - Object[] row = null; - if (cursor != null) { - int colCount = cursor.getColumnCount(); - row = new Object[colCount]; - for (int indx = 0; indx < colCount; indx++) { - row[indx] = getFieldValueFromRow(cursor, indx); - } - } - return row; - } - - public static Object getFieldValueFromRow(Cursor cursor, int position) { - switch (cursor.getType(position)) { - case Cursor.FIELD_TYPE_BLOB: return cursor.getBlob(position); - case Cursor.FIELD_TYPE_FLOAT: return cursor.getFloat(position); - case Cursor.FIELD_TYPE_INTEGER: return cursor.getInt(position); - case Cursor.FIELD_TYPE_STRING: return cursor.getString(position); - case Cursor.FIELD_TYPE_NULL: - default: - return null; - } - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/util/FontConfigParser.java b/src/com/cyngn/theme/util/FontConfigParser.java deleted file mode 100644 index ae963b8..0000000 --- a/src/com/cyngn/theme/util/FontConfigParser.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.util.Xml; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * Parses an XML font config. Example: - * - * - * - * - * - * sans-serif - * arial - * - * - * Roboto-Regular.ttf - * Roboto-Bold.ttf - * Roboto-Italic.ttf - * Roboto-BoldItalic.ttf - * - * - * - * ... - * - * - */ -public class FontConfigParser { - - public static class Family { - public List nameset = new ArrayList(); - public List fileset = new ArrayList(); - } - - public static List parse(InputStream in) throws XmlPullParserException, IOException { - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); - parser.nextTag(); - return readFamilySet(parser); - } finally { - in.close(); - } - } - - private static List readFamilySet(XmlPullParser parser) throws XmlPullParserException, IOException { - List families = new ArrayList(); - parser.require(XmlPullParser.START_TAG, null, "familyset"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String name = parser.getName(); - - // Starts by looking for the entry tag - if (name.equals("family")) { - Family family = readFamily(parser); - families.add(family); - } - } - return families; - } - - private static Family readFamily(XmlPullParser parser) throws XmlPullParserException, IOException { - Family family = new Family(); - parser.require(XmlPullParser.START_TAG, null, "family"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String name = parser.getName(); - if (name.equals("nameset")) { - List nameset = readNameset(parser); - family.nameset = nameset; - } else if (name.equals("fileset")) { - List fileset = readFileset(parser); - family.fileset = fileset; - } else { - skip(parser); - } - } - return family; - } - - private static List readNameset(XmlPullParser parser) throws XmlPullParserException, IOException { - List names = new ArrayList(); - parser.require(XmlPullParser.START_TAG, null, "nameset"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String tagname = parser.getName(); - if (tagname.equals("name")) { - String name = readText(parser); - names.add(name); - } else { - skip(parser); - } - } - return names; - } - - private static List readFileset(XmlPullParser parser) throws XmlPullParserException, IOException { - List files = new ArrayList(); - parser.require(XmlPullParser.START_TAG, null, "fileset"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String name = parser.getName(); - if (name.equals("file")) { - String file = readText(parser); - files.add(file); - } else { - skip(parser); - } - } - return files; - } - - // For the tags title and summary, extracts their text values. - private static String readText(XmlPullParser parser) throws IOException, XmlPullParserException { - String result = ""; - if (parser.next() == XmlPullParser.TEXT) { - result = parser.getText(); - parser.nextTag(); - } - return result; - } - - private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { - if (parser.getEventType() != XmlPullParser.START_TAG) { - throw new IllegalStateException(); - } - int depth = 1; - while (depth != 0) { - switch (parser.next()) { - case XmlPullParser.END_TAG: - depth--; - break; - case XmlPullParser.START_TAG: - depth++; - break; - } - } - } -} diff --git a/src/com/cyngn/theme/util/IconPreviewHelper.java b/src/com/cyngn/theme/util/IconPreviewHelper.java deleted file mode 100644 index 60ef9c8..0000000 --- a/src/com/cyngn/theme/util/IconPreviewHelper.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.ActivityManager; -import android.app.ComposedIconInfo; -import android.app.IconPackHelper; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.SparseArray; - -/** - * This class handles all the logic to build a preview icon - * If the system currently has a theme applied we do NOT - * want this code to be impacted by it. So code in this - * class creates special "no theme attached" resource objects - * to retrieve objects from. - */ -public class IconPreviewHelper { - private static final String TAG = IconPreviewHelper.class.getSimpleName(); - private final static float ICON_SCALE_FACTOR = 1.3f; //Arbitrary. Looks good - - private Context mContext; - private DisplayMetrics mDisplayMetrics; - private Configuration mConfiguration; - private int mIconDpi = 0; - private String mThemePkgName; - private IconPackHelper mIconPackHelper; - private int mIconSize; - - /** - * @param themePkgName - The package name of the theme we wish to preview - */ - public IconPreviewHelper(Context context, String themePkgName) { - mContext = context; - mDisplayMetrics = context.getResources().getDisplayMetrics(); - mConfiguration = context.getResources().getConfiguration(); - ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - mIconDpi = (int) (am.getLauncherLargeIconDensity() * ICON_SCALE_FACTOR); - mThemePkgName = themePkgName; - mIconPackHelper = new IconPackHelper(mContext); - try { - mIconPackHelper.loadIconPack(mThemePkgName); - } catch (NameNotFoundException e) {} - mIconSize = (int) (am.getLauncherLargeIconSize() * ICON_SCALE_FACTOR); - } - - /** - * Returns the actual label name for a given component - * If the activity does not have a label it will return app's label - * If neither has a label returns empty string - */ - public String getLabel(ComponentName component) { - String label = ""; - try { - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo appInfo = pm.getApplicationInfo(component.getPackageName(), 0); - ActivityInfo activityInfo = pm.getActivityInfo(component, 0); - - AssetManager assets = new AssetManager(); - assets.addAssetPath(appInfo.publicSourceDir); - Resources res = new Resources(assets, mDisplayMetrics, mConfiguration); - - if (activityInfo.labelRes != 0) { - label = res.getString(activityInfo.labelRes); - } else if (appInfo.labelRes != 0) { - label = res.getString(appInfo.labelRes); - } - } catch(NameNotFoundException exception) { - Log.e(TAG, "unable to find pkg for " + component.toString()); - } - return label; - } - - /** - * Returns the icon for the given component regardless of the system's - * currently applied theme. If the preview theme does not support the icon, then - * return the system default icon. - */ - public Drawable getIcon(ComponentName component) { - String packageName = component.getPackageName(); - String activityName = component.getClassName(); - Drawable icon = getThemedIcon(packageName, activityName); - if (icon == null) { - icon = getDefaultIcon(packageName, activityName); - } - if (icon != null) { - icon.setBounds(0, 0, mIconSize, mIconSize); - } - return icon; - } - - private Drawable getThemedIcon(String pkgName, String activityName) { - Drawable drawable = null; - ActivityInfo info = new ActivityInfo(); - info.packageName = pkgName; - info.name = activityName; - drawable = mIconPackHelper.getDrawableForActivityWithDensity(info, mIconDpi); - - return drawable; - } - - /** - * Returns the default icon. This can be the normal icon associated with the app or a composed - * icon if the icon pack supports background, mask, and/or foreground. - * @param pkgName - * @param activityName - * @return - */ - public Drawable getDefaultIcon(String pkgName, String activityName) { - Drawable drawable = null; - ComponentName component = new ComponentName(pkgName, activityName); - PackageManager pm = mContext.getPackageManager(); - Resources res = null; - try { - ActivityInfo info = pm.getActivityInfo(component, 0); - ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, 0); - - AssetManager assets = new AssetManager(); - assets.addAssetPath(appInfo.publicSourceDir); - res = new Resources(assets, mDisplayMetrics, mConfiguration); - - final int iconId = info.icon != 0 ? info.icon : appInfo.icon; - info.themedIcon = 0; - setupComposedIcon(res, info, iconId); - drawable = getFullResIcon(res, iconId); - } catch (NameNotFoundException e2) { - Log.w(TAG, "Unable to get the icon for " + pkgName + " using default"); - } - drawable = (drawable != null) ? - getComposedIcon(res, drawable) : getFullResDefaultActivityIcon(); - return drawable; - } - - private Drawable getComposedIcon(Resources res, Drawable baseIcon) { - ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); - if (res != null && iconInfo != null && (iconInfo.iconBacks != null || - iconInfo.iconMask != 0 || iconInfo.iconUpon != 0)) { - return IconPackHelper.IconCustomizer.getComposedIconDrawable(baseIcon, res, iconInfo); - } - return baseIcon; - } - - private void setupComposedIcon(Resources res, ActivityInfo info, int iconId) { - ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); - if (iconInfo.iconBacks == null && iconInfo.iconMask == 0 && iconInfo.iconUpon == 0) { - return; - } - - res.setComposedIconInfo(iconInfo); - - SparseArray icons = new SparseArray(1); - info.themedIcon = 0; - icons.put(iconId, info); - res.setIconResources(icons); - } - - private Drawable getFullResIcon(Resources resources, int iconId) { - Drawable d; - try { - d = resources.getDrawableForDensity(iconId, mIconDpi, null, false); - } catch (Resources.NotFoundException e) { - d = null; - } - return (d != null) ? d : getFullResDefaultActivityIcon(); - } - - private Drawable getFullResDefaultActivityIcon() { - return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); - } -} diff --git a/src/com/cyngn/theme/util/NotificationHelper.java b/src/com/cyngn/theme/util/NotificationHelper.java deleted file mode 100644 index 3ae68d7..0000000 --- a/src/com/cyngn/theme/util/NotificationHelper.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.BitmapFactory; -import android.text.TextUtils; - -import com.cyngn.theme.chooser.ChooserActivity; -import com.cyngn.theme.chooser.R; - -public class NotificationHelper { - private static final int NOTIFICATION_ID = 0x434D5443; - - public static void postThemeInstalledNotification(Context context, String pkgName) { - String themeName = null; - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); - if (pi.themeInfo != null) { - themeName = pi.themeInfo.name; - } - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return; - } - if (TextUtils.isEmpty(themeName)) { - return; - } - - int themeCount = PreferenceUtils.getNewlyInstalledThemeCount(context) + 1; - - Intent intent = new Intent(context, ChooserActivity.class); - intent.setAction(Intent.ACTION_MAIN); - intent.putExtra("pkgName", pkgName); - PendingIntent pi = PendingIntent.getActivity(context, 0, intent, - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); - - String title = null; - String content = null; - final Resources res = context.getResources(); - if (themeCount == 1) { - title = String.format(res.getString( - R.string.theme_installed_notification_title), themeName); - content = res.getString(R.string.theme_installed_notification_text); - } else { - title = String.format(res.getString(R.string.themes_installed_notification_title), - themeCount); - content = String.format(res.getQuantityString( - R.plurals.themes_installed_notification_text, themeCount -1), - themeName, themeCount - 1); - } - NotificationManager nm = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - Notification notice = new Notification.Builder(context) - .setAutoCancel(true) - .setOngoing(false) - .setContentTitle(title) - .setContentText(content) - .setContentIntent(pi) - .setSmallIcon(R.drawable.ic_notify) - .setWhen(System.currentTimeMillis()) - .build(); - if (themeCount > 1) notice.number = themeCount; - nm.notify(NOTIFICATION_ID, notice); - PreferenceUtils.setNewlyInstalledThemeCount(context, themeCount); - } - - public static void cancelNotifications(Context context) { - NotificationManager nm = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(NOTIFICATION_ID); - PreferenceUtils.setNewlyInstalledThemeCount(context, 0); - } -} diff --git a/src/com/cyngn/theme/util/PreferenceUtils.java b/src/com/cyngn/theme/util/PreferenceUtils.java deleted file mode 100644 index 6444c41..0000000 --- a/src/com/cyngn/theme/util/PreferenceUtils.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.ThemeUtils; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.text.TextUtils; -import android.util.Log; - -import java.util.HashSet; -import java.util.Set; - -public class PreferenceUtils { - private static final String TAG = PreferenceUtils.class.getSimpleName(); - - public static final String PREF_APPLIED_BASE_THEME = "applied_base_theme"; - public static final String PREF_UPDATED_THEMES = "updated_themes"; - public static final String PREF_NEWLY_INSTALLED_THEME_COUNT = "newly_installed_theme_count"; - public static final String PREF_INSTALLED_THEMES_PROCESSING = "installed_themes_processing"; - public static final String PREF_SHOW_PER_APP_THEMING_NEW_TAG = "show_per_app_new_tag"; - - public static SharedPreferences getSharedPreferences(Context context) { - if (context == null) return null; - return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); - } - - public static String getAppliedBaseTheme(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs == null) return null; - - final Resources res = context.getResources(); - final ThemeConfig config = res.getConfiguration().themeConfig; - String appliedTheme = config != null - ? config.getOverlayPkgName() - : ThemeConfig.SYSTEM_DEFAULT; - return prefs.getString(PREF_APPLIED_BASE_THEME, appliedTheme); - } - - public static void setAppliedBaseTheme(Context context, String pkgName) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - prefs.edit().putString(PREF_APPLIED_BASE_THEME, pkgName).apply(); - } - } - - public static Set getUpdatedThemes(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs == null) return null; - - return prefs.getStringSet(PREF_UPDATED_THEMES, null); - } - - public static void addUpdatedTheme(Context context, String pkgName) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - Set updatedThemes = new HashSet(); - Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); - if (current != null) { - updatedThemes.addAll(current); - } - if (updatedThemes.add(pkgName)) { - prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); - } - } - } - - public static void removeUpdatedTheme(Context context, String pkgName) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - Set updatedThemes = new HashSet(); - Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); - if (current != null) { - updatedThemes.addAll(current); - } - if (updatedThemes.remove(pkgName)) { - prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); - } - } - } - - public static boolean hasThemeBeenUpdated(Context context, String pkgName) { - Set updatedThemes = getUpdatedThemes(context); - return updatedThemes != null && updatedThemes.contains(pkgName); - } - - public static int getNewlyInstalledThemeCount(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs == null) return 0; - - return prefs.getInt(PREF_NEWLY_INSTALLED_THEME_COUNT, 0); - } - - public static void setNewlyInstalledThemeCount(Context context, int count) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - prefs.edit().putInt(PREF_NEWLY_INSTALLED_THEME_COUNT, count).apply(); - } - } - - public static boolean getShowPerAppThemeNewTag(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - return prefs.getBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, true); - } - - return false; - } - - public static void setShowPerAppThemeNewTag(Context context, boolean show) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - prefs.edit().putBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, show).apply(); - } - } -} diff --git a/src/com/cyngn/theme/util/ThemedTypefaceHelper.java b/src/com/cyngn/theme/util/ThemedTypefaceHelper.java deleted file mode 100644 index ce38f80..0000000 --- a/src/com/cyngn/theme/util/ThemedTypefaceHelper.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.content.res.AssetManager; -import android.graphics.FontListParser; -import android.graphics.FontListParser.Family; -import android.graphics.Typeface; -import android.util.Log; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.List; - -/** - * Assists in loading a themes font typefaces. - * Will load system default if there is a load issue - */ -public class ThemedTypefaceHelper { - private static final String TAG = ThemedTypefaceHelper.class.getName(); - private static final String FAMILY_SANS_SERIF = "sans-serif"; - private static final String FONTS_DIR = "fonts"; - private static final String SYSTEM_FONTS_XML = "/system/etc/system_fonts.xml"; - private static final String SYSTEM_FONTS_DIR = "/system/fonts"; - - private boolean mIsLoaded; - private Context mThemeContext; - private List mFamilies; - private Typeface[] mTypefaces = new Typeface[4]; - - public void load(Context context, String pkgName) { - try { - loadThemedFonts(context, pkgName); - return; - } catch(Exception e) { - Log.w(TAG, "Unable to parse and load themed fonts for " + pkgName + - ". Falling back to system fonts", e ); - } - - try { - loadSystemFonts(); - return; - } catch(Exception e) { - Log.e(TAG, "Parsing system fonts failed. Falling back to Typeface loaded fonts", e); - } - - // There is no reason for this to happen unless someone - // messed up the system_fonts.xml - loadDefaultFonts(); - } - - private void loadThemedFonts(Context context, String pkgName) throws Exception { - //Parse the font XML - mThemeContext = context.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY); - AssetManager assetManager = mThemeContext.getAssets(); - InputStream is = assetManager.open(FONTS_DIR + File.separator + ThemeUtils.FONT_XML); - FontListParser.Config fontConfig = FontListParser.parse(is, FONTS_DIR); - mFamilies = fontConfig.families; - - //Load the typefaces for sans-serif - Family sanSerif = getFamily(FAMILY_SANS_SERIF); - mTypefaces[Typeface.NORMAL] = loadTypeface(sanSerif, Typeface.NORMAL); - mTypefaces[Typeface.BOLD] = loadTypeface(sanSerif, Typeface.BOLD); - mTypefaces[Typeface.ITALIC] = loadTypeface(sanSerif, Typeface.ITALIC); - mTypefaces[Typeface.BOLD_ITALIC] = loadTypeface(sanSerif, Typeface.BOLD_ITALIC); - mIsLoaded = true; - } - - private void loadSystemFonts() throws Exception { - //Parse the system font XML - File file = new File(SYSTEM_FONTS_XML); - InputStream is = new FileInputStream(file); - FontListParser.Config fontConfig = FontListParser.parse(is, SYSTEM_FONTS_DIR); - mFamilies = fontConfig.families; - - //Load the typefaces for sans-serif - Family sanSerif = getFamily(FAMILY_SANS_SERIF); - if (mTypefaces[Typeface.NORMAL] == null) { - mTypefaces[Typeface.NORMAL] = loadSystemTypeface(sanSerif, Typeface.NORMAL); - } - if (mTypefaces[Typeface.BOLD] == null) { - mTypefaces[Typeface.BOLD] = loadSystemTypeface(sanSerif, Typeface.BOLD); - } - if (mTypefaces[Typeface.ITALIC] == null) { - mTypefaces[Typeface.ITALIC] = loadSystemTypeface(sanSerif, Typeface.ITALIC); - } - if (mTypefaces[Typeface.BOLD_ITALIC] == null) { - mTypefaces[Typeface.BOLD_ITALIC] = loadSystemTypeface(sanSerif, Typeface.BOLD_ITALIC); - } - mIsLoaded = true; - } - - private void loadDefaultFonts() { - mTypefaces[Typeface.NORMAL] = Typeface.DEFAULT; - mTypefaces[Typeface.BOLD] = Typeface.DEFAULT_BOLD; - mIsLoaded = true; - } - - private Family getFamily(String familyName) throws Exception { - for(Family family : mFamilies) { - if (family.name.equals(familyName)) { - return family; - } - } - throw new Exception("Unable to find " + familyName); - } - - private Typeface loadTypeface(Family family, int style) { - AssetManager assets = mThemeContext.getAssets(); - String path = family.fonts.get(style).fontName; - return Typeface.createFromAsset(assets, path); - } - - private Typeface loadSystemTypeface(Family family, int style) { - return Typeface.createFromFile(family.fonts.get(style).fontName); - } - - public Typeface getTypeface(int style) { - if (!mIsLoaded) throw new IllegalStateException("Helper was not loaded"); - return mTypefaces[style]; - } -} diff --git a/src/com/cyngn/theme/util/TypefaceHelperCache.java b/src/com/cyngn/theme/util/TypefaceHelperCache.java deleted file mode 100644 index ced93be..0000000 --- a/src/com/cyngn/theme/util/TypefaceHelperCache.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.content.Context; - -import java.util.HashMap; -import java.util.Map; - -public class TypefaceHelperCache { - private static TypefaceHelperCache sHelperCache; - private final Map mCache; - - private TypefaceHelperCache() { - mCache = new HashMap(); - } - - public static synchronized TypefaceHelperCache getInstance() { - if (sHelperCache == null) { - sHelperCache = new TypefaceHelperCache(); - } - return sHelperCache; - } - - public ThemedTypefaceHelper getHelperForTheme(Context context, String pkgName) { - synchronized (mCache) { - ThemedTypefaceHelper helper = mCache.get(pkgName); - if (helper == null) { - helper = new ThemedTypefaceHelper(); - helper.load(context, pkgName); - mCache.put(pkgName, helper); - } - return helper; - } - } - - public int getTypefaceCount() { - synchronized (mCache) { - return mCache.size(); - } - } -} diff --git a/src/com/cyngn/theme/util/Utils.java b/src/com/cyngn/theme/util/Utils.java deleted file mode 100644 index a0b910d..0000000 --- a/src/com/cyngn/theme/util/Utils.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.ActivityManager; -import android.app.WallpaperManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.RemoteException; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; -import android.util.TypedValue; -import android.view.IWindowManager; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; - -import com.cyngn.theme.chooser.ChooserActivity; - -import cyanogenmod.externalviews.KeyguardExternalView; -import cyanogenmod.providers.CMSettings; -import cyanogenmod.providers.ThemesContract; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.security.InvalidParameterException; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import static android.content.res.ThemeConfig.SYSTEM_DEFAULT; - -public class Utils { - private static final String TAG = Utils.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final String OVERLAY_BASE_PATH = "overlays" + File.separator; - - public static Bitmap decodeFile(String path, int reqWidth, int reqHeight) { - BitmapFactory.Options opts = new BitmapFactory.Options(); - - // Determine insample size - opts.inJustDecodeBounds = true; - BitmapFactory.decodeFile(path, opts); - opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); - - // Decode the bitmap, regionally if necessary - Bitmap bitmap = null; - opts.inJustDecodeBounds = false; - Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); - try { - if (rect != null) { - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false); - // Check if we can downsample more now that we cropped - opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), - reqWidth, reqHeight); - bitmap = decoder.decodeRegion(rect, opts); - } else { - bitmap = BitmapFactory.decodeFile(path, opts); - } - } catch (IOException e) { - Log.e(TAG, "Unable to open resource in path" + path, e); - } - return bitmap; - } - - public static Bitmap decodeResource(Resources res, int resId, int reqWidth, int reqHeight) { - BitmapFactory.Options opts = new BitmapFactory.Options(); - - // Determine insample size - opts.inJustDecodeBounds = true; - BitmapFactory.decodeResource(res, resId, opts); - opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); - - // Decode the bitmap, regionally if necessary - Bitmap bitmap = null; - opts.inJustDecodeBounds = false; - Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); - - InputStream stream = null; - try { - if (rect != null) { - stream = res.openRawResource(resId, new TypedValue()); - if (stream == null) return null; - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(stream, false); - // Check if we can downsample a little more now that we cropped - opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), - reqWidth, reqHeight); - bitmap = decoder.decodeRegion(rect, opts); - } else { - bitmap = BitmapFactory.decodeResource(res, resId, opts); - } - } catch (IOException e) { - Log.e(TAG, "Unable to open resource " + resId, e); - } finally { - closeQuiet(stream); - } - return bitmap; - } - - - public static Bitmap getBitmapFromAsset(Context ctx, String path,int reqWidth, int reqHeight) { - if (ctx == null || path == null) - return null; - - String ASSET_BASE = "file:///android_asset/"; - path = path.substring(ASSET_BASE.length()); - - - Bitmap bitmap = null; - try { - AssetManager assets = ctx.getAssets(); - InputStream is = assets.open(path); - - // Determine insample size - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, null, opts); - opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); - is.close(); - - // Decode the bitmap, regionally if neccessary - is = assets.open(path); - opts.inJustDecodeBounds = false; - Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); - if (rect != null) { - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false); - // Check if we can downsample a little more now that we cropped - opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), - reqWidth, reqHeight); - bitmap = decoder.decodeRegion(rect, opts); - } else { - bitmap = BitmapFactory.decodeStream(is); - } - } catch (IOException e) { - e.printStackTrace(); - } - return bitmap; - } - - - /** - * For excessively large images with an awkward ratio we - * will want to crop them - * @return - */ - public static Rect getCropRectIfNecessary( - BitmapFactory.Options options,int reqWidth, int reqHeight) { - Rect rect = null; - // Determine downsampled size - int width = options.outWidth / options.inSampleSize; - int height = options.outHeight / options.inSampleSize; - - if ((reqHeight * 1.5 < height)) { - int bottom = height/ 4; - int top = bottom + height/2; - rect = new Rect(0, bottom, width, top); - } else if ((reqWidth * 1.5 < width)) { - int left = width / 4; - int right = left + height/2; - rect = new Rect(left, 0, right, height); - } - return rect; - } - - public static int calculateInSampleSize( - BitmapFactory.Options options, int reqWidth, int reqHeight) { - return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); - } - - // Modified from original source: - // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html - public static int calculateInSampleSize( - int decodeWidth, int decodeHeight, int reqWidth, int reqHeight) { - // Raw height and width of image - int inSampleSize = 1; - - if (decodeHeight > reqHeight || decodeWidth > reqWidth) { - final int halfHeight = decodeHeight / 2; - final int halfWidth = decodeWidth / 2; - - // Calculate the largest inSampleSize value that is a power of 2 and keeps both - // height and width larger than the requested height and width. - while ((halfHeight / inSampleSize) > reqHeight && - (halfWidth / inSampleSize) > reqWidth) { - inSampleSize *= 2; - } - } - - return inSampleSize; - } - - public static InputStream getInputStreamFromAsset( - Context ctx, String path) throws IOException { - if (ctx == null || path == null) - return null; - InputStream is = null; - String ASSET_BASE = "file:///android_asset/"; - path = path.substring(ASSET_BASE.length()); - AssetManager assets = ctx.getAssets(); - is = assets.open(path); - return is; - } - - public static void copy(InputStream is, OutputStream os) throws IOException { - final byte[] bytes = new byte[4096]; - int len; - while ((len = is.read(bytes)) > 0) { - os.write(bytes, 0, len); - } - } - - public static void closeQuiet(InputStream stream) { - if (stream == null) - return; - try { - stream.close(); - } catch (IOException e) { - } - } - - public static void closeQuiet(OutputStream stream) { - if (stream == null) - return; - try { - stream.close(); - } catch (IOException e) { - } - } - - //Note: will not delete populated subdirs - public static void deleteFilesInDir(String dirPath) { - File fontDir = new File(dirPath); - File[] files = fontDir.listFiles(); - if (files != null) { - for(File file : fontDir.listFiles()) { - file.delete(); - } - } - } - - public static boolean hasNavigationBar(Context context) { - boolean needsNavigationBar = false; - try { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - needsNavigationBar = wm.needsNavigationBar(); - } catch (RemoteException e) { - } - // Need to also check for devices with hardware keys where the user has chosen to use - // the on screen navigation bar - needsNavigationBar = needsNavigationBar || - CMSettings.Secure.getInt(context.getContentResolver(), - CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR, 0) == 1; - return needsNavigationBar; - } - - public static Bitmap loadBitmapBlob(Cursor cursor, int columnIdx) { - if (columnIdx < 0) { - Log.w(TAG, "loadBitmapBlob(): Invalid index provided, returning null"); - return null; - } - - if (cursor.getType(columnIdx) == Cursor.FIELD_TYPE_STRING) { - return loadBitmapFile(cursor, columnIdx); - } - - byte[] blob = cursor.getBlob(columnIdx); - if (blob == null) return null; - return BitmapFactory.decodeByteArray(blob, 0, blob.length); - } - - public static Bitmap loadBitmapFile(Cursor cursor, int columnIdx) { - if (columnIdx < 0) { - Log.w(TAG, "loadBitmapFile(): Invalid index provided, returning null"); - return null; - } - String path = cursor.getString(columnIdx); - if (TextUtils.isEmpty(path)) { - return null; - } - - Bitmap image = null; - FileInputStream inputStream; - try { - inputStream = new FileInputStream(path); - image = BitmapFactory.decodeStream(inputStream); - inputStream.close(); - } catch (Exception e) { - Log.w(TAG, "Unable to open preview " + path, e); - } - - return image; - } - - public static String getBatteryIndex(int type) { - switch(type) { - case 2: - return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_CIRCLE; - case 5: - return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE; - default: - return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_PORTRAIT; - } - } - - public static Bitmap getRegularWallpaperBitmap(Context context) { - WallpaperManager wm = WallpaperManager.getInstance(context); - - Bitmap bitmap = null; - // desktop wallpaper here - Bitmap wallpaper = wm.getBitmap(); - if (wallpaper == null) { - return null; - } - - Point size = new Point(); - WindowManager windowManager = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getRealSize(size); - - final int dw = size.x; - final int dh = size.y; - - // Center the scaled image - float scale = Math.max(1f, Math.max(dw / (float) wallpaper.getWidth(), - dh / (float) wallpaper.getHeight())); - - final int scaledWidth = Math.round((wallpaper.getWidth() * scale)); - final int scaledHeight = Math.round((wallpaper.getHeight() * scale)); - - // TODO: set xOffset to wm.getLastWallpaperX() once available - int xOffset = wm.getLastWallpaperX(); - // x offset - if (xOffset == -1) { - xOffset = 0; - } else { - xOffset *= -1; - } - - // y offsets - // TODO: set yOffset to wm.getLastWallpaperY() once available - int yOffset = wm.getLastWallpaperY(); - if (yOffset == -1) { - yOffset = 0; - } else { - yOffset *= -1; - } - - if (DEBUG) { - Log.d(TAG, "scale: " + scale); - Log.d(TAG, "scaledWidth: " + scaledWidth); - Log.d(TAG, "scaledHeight: " + scaledHeight); - Log.d(TAG, "wallpaper size: width: " + wallpaper.getWidth() + - ", height: " + wallpaper.getHeight()); - Log.d(TAG, "xOffset: " + xOffset); - Log.d(TAG, "yOffset: " + yOffset); - } - - try { - if (wallpaper.getHeight() < dh) { - // need to scale it up vertically - - if (wallpaper.getHeight() > wallpaper.getWidth()) { - // handle portrait wallpaper - float diff = scaledWidth - dw; - int diffhalf = Math.round(diff / 2); - - bitmap = Bitmap.createScaledBitmap(wallpaper, scaledWidth, scaledHeight, true); - bitmap = Bitmap.createBitmap(bitmap, diffhalf, 0, dw, dh); - bitmap = Bitmap.createBitmap(bitmap, xOffset, 0, dw, dh); - } else { - int goldenWidth = Math.round(wallpaper.getHeight() * 1.125f); - int spaceA = (wallpaper.getWidth() - goldenWidth) / 2; - int spaceB = (goldenWidth - Math.round(dh / scale)) / 2; - - bitmap = Bitmap.createBitmap(wallpaper, spaceA, 0, goldenWidth, - wallpaper.getHeight()); - int left = spaceB + Math.round(xOffset / scale); - bitmap = Bitmap.createBitmap(bitmap, left, 0, Math.round(dw / scale), - Math.round(dh / scale)); - } - - } else if (wallpaper.getWidth() < dw) { - // need to scale it up horizontally - - if (wallpaper.getHeight() > wallpaper.getWidth()) { - // handle portrait wallpaper - return wallpaper; - - } else { - // handle landscape wallpaper - float diff = wallpaper.getHeight() - wallpaper.getWidth(); - int diffhalf = Math.round(diff / 2); - - if (diffhalf < 0) { - return wallpaper; - } - - bitmap = Bitmap.createBitmap( - wallpaper, diffhalf, 0, - wallpaper.getWidth(), wallpaper.getWidth()); - - // blow it up - bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledWidth, true); - - bitmap = Bitmap.createBitmap(bitmap, 0, 0, dw, dh); - } - - } else { - // sometimes the wallpaper manager gives incorrect offsets, - // and adds like 200 pixels randomly. If it's bigger than we can handle, calculate - // our own :) - if (yOffset + dh > wallpaper.getHeight()) { - yOffset = (wallpaper.getHeight() - dh) / 2; - } - if (xOffset + dw > wallpaper.getWidth()) { - yOffset = (wallpaper.getWidth() - dw) / 2; - } - bitmap = Bitmap.createBitmap(wallpaper, xOffset, yOffset, dw, dh); - } - } catch (IllegalArgumentException e) { - // Cropping/resizing failed so return the original - bitmap = wallpaper; - } - return bitmap; - } - - public static boolean isRecentTaskHome(Context context) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - final List recentTasks = am.getRecentTasks( - 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); - if (recentTasks.size() > 1) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(1); - - Intent intent = new Intent(recentInfo.baseIntent); - if (recentInfo.origActivity != null) { - intent.setComponent(recentInfo.origActivity); - } - - // Now check if this recent task is a launcher - if (isCurrentHomeActivity(context, intent.getComponent())) { - return true; - } - } - return false; - } - - public static boolean isRecentTaskThemeStore(Context context) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - final List recentTasks = am.getRecentTasks( - 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); - if (recentTasks.size() > 0) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); - - Intent intent = new Intent(recentInfo.baseIntent); - if (recentInfo.origActivity != null) { - intent.setComponent(recentInfo.origActivity); - } - - if (intent.getComponent() - .getPackageName().equals(ChooserActivity.THEME_STORE_PACKAGE)) { - return true; - } - } - return false; - } - - - public static String getTopTaskPackageName(Context context) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - final List recentTasks = am.getRecentTasks(1, 0); - if (recentTasks.size() > 0) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); - if (recentInfo.origActivity != null) { - return recentInfo.origActivity.getPackageName(); - } - if (recentInfo.baseIntent != null) { - return recentInfo.baseIntent.getComponent().getPackageName(); - } - } - return null; - } - - public static boolean hasPerAppThemesApplied(Context context) { - final Configuration config = context.getResources().getConfiguration(); - final ThemeConfig themeConfig = config != null ? config.themeConfig : null; - if (themeConfig != null) { - Map themes = themeConfig.getAppThemes(); - for (String appPkgName : themes.keySet()) { - if (ThemeUtils.isPerAppThemeComponent(appPkgName)) { - return true; - } - } - } - return false; - } - - /** - * Method to identify if a theme explicitly overlays a particular app. Explicit is defined - * as having files in overlays/appPkgName/ - * @param context - * @param appPkgNane - * @param themePkgName - * @return - */ - public static boolean themeHasOverlayForApp(Context context, String appPkgNane, - String themePkgName) { - boolean hasExplicitOverlay = false; - if (ThemeConfig.SYSTEM_DEFAULT.equals(themePkgName)) { - hasExplicitOverlay = true; - } else { - try { - Context themeContext = context.createPackageContext(themePkgName, 0); - if (themeContext != null) { - AssetManager assets = themeContext.getAssets(); - String[] files = assets.list(OVERLAY_BASE_PATH + appPkgNane); - if (files != null && files.length > 0) hasExplicitOverlay = true; - } - } catch (Exception e) { - // don't care, we'll return false and let the caller handle things - } - } - return hasExplicitOverlay; - } - - private static boolean isCurrentHomeActivity(Context context, - ComponentName component) { - final PackageManager pm = context.getPackageManager(); - ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) - .resolveActivityInfo(pm, 0); - - return homeInfo != null - && homeInfo.packageName.equals(component.getPackageName()) - && homeInfo.name.equals(component.getClassName()); - } - - /** - * Returns the resource-IDs for all attributes specified in the given - * -resource tag as an int array. - * stackoverflow.com/questions/13816596/accessing-declare-styleable-resources-programatically - * - * @param name - * @return - */ - public static final int[] getResourceDeclareStyleableIntArray(String pkgName, String name) { - try { - //use reflection to access the resource class - Field[] fields2 = - Class.forName(pkgName + ".R$styleable").getFields(); - - //browse all fields - for (Field f : fields2) { - //pick matching field - if (f.getName().equals(name)) { - //return as int array - int[] ret = (int[])f.get(null); - return ret; - } - } - } - catch (Throwable t) { - } - - return null; - } - - /** - * Retrieves the list of dangerous permissions not granted to the supplied package. This method - * is not capable of identifying if a given permission was previously revoked by the user or - * if the user decided not to be asked again. - * - * @param context - * @param pkgName - * @return Returns an array of Strings with the name of the permissions. An empty array will - * be returned if all dangerous permissions have been already granted. - */ - public static String[] getDangerousPermissionsNotGranted(Context context, String pkgName) - throws InvalidParameterException { - LinkedList permissionsNotGranted = new LinkedList(); - PackageInfo pkgInfo = null; - PackageManager pm = context.getPackageManager(); - try { - pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS); - } catch (PackageManager.NameNotFoundException e) { - throw new InvalidParameterException("Package " + pkgName + " not found"); - } - - String[] requestedPermissions = pkgInfo.requestedPermissions; - int[] requestedPermissionsFlags = pkgInfo.requestedPermissionsFlags; - - for (int indx = 0; indx < requestedPermissions.length; indx++) { - if ((requestedPermissionsFlags[indx] & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { - try { - PermissionInfo pi = pm.getPermissionInfo(requestedPermissions[indx],0); - - if (pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { - permissionsNotGranted.add(requestedPermissions[indx]); - if (DEBUG) { - Log.d(TAG, "Permission " + requestedPermissions[indx] + "not granted"); - } - } - } catch(PackageManager.NameNotFoundException e) { - //If package manager doesn't know of the permission we just continue since - //this permission won't end up in the list - } - } - } - return permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]); - } - - /** - * Builds a ComponentName to identify the activity associated with the - * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category in the given package - * @param context - * @param packageName - * @return Returns the ComponentName or null if no activity was found. - */ - private static ComponentName getPermissionGranterComponentName(Context context, - String packageName) { - Intent rIntent = new Intent() - .setPackage(packageName) - .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION); - - List resolveInfo = context.getPackageManager(). - queryIntentActivities(rIntent, PackageManager.GET_RESOLVED_FILTER); - - if (resolveInfo.size() >= 1) { - if (DEBUG) { - if (resolveInfo.size() >= 2) { - Log.w(TAG, "Got " + resolveInfo.size() + " resolvers! Defaulting to " - + resolveInfo.get(0).activityInfo.name); - } - } - } - return (resolveInfo.size() >=1 ) ? - new ComponentName(packageName, resolveInfo.get(0).activityInfo.name) : - null; - } - - /** - * Builds an intent used to request the user to grant or revoke the supplied permissions. - * The intent will set the KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION - * category and an extra containing the list of permissions identified by - * KeyguardExternalView.EXTRA_PERMISSION_LIST - * @param context - * @param packageName - * @param permissionList - * @return Returns the intent if an activity associated with - * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category was found. Otherwise, it - * returns null - */ - public static Intent buildPermissionGrantRequestIntent(Context context, String packageName, - String[] permissionList) { - ComponentName componentName = getPermissionGranterComponentName(context, packageName); - if (componentName == null) return null; - - Intent permissionIntent = new Intent() - .setComponent(componentName) - .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION) - .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(KeyguardExternalView.EXTRA_PERMISSION_LIST, permissionList); - return permissionIntent; - } - - public static String getDefaultThemePackageName(Context context) { - final String defaultThemePkg = CMSettings.Secure.getString(context.getContentResolver(), - CMSettings.Secure.DEFAULT_THEME_PACKAGE); - if (!TextUtils.isEmpty(defaultThemePkg)) { - PackageManager pm = context.getPackageManager(); - try { - if (pm.getPackageInfo(defaultThemePkg, 0) != null) { - return defaultThemePkg; - } - } catch (PackageManager.NameNotFoundException e) { - // doesn't exist so system will be default - Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e); - } - } - - return SYSTEM_DEFAULT; - } -} diff --git a/src/com/cyngn/theme/util/WallpaperUtils.java b/src/com/cyngn/theme/util/WallpaperUtils.java deleted file mode 100644 index 7f7536c..0000000 --- a/src/com/cyngn/theme/util/WallpaperUtils.java +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.WallpaperManager; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; -import android.os.AsyncTask; -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -public class WallpaperUtils { - private static final String TAG = WallpaperUtils.class.getSimpleName(); - private static final int DEFAULT_COMPRESS_QUALITY = 90; - - /** - * createThumbnail from WallpaperCropActivity in f/b/packages/WallpaperCropper, renamed - * tp createPreview. - */ - public static Bitmap createPreview(Point size, Context context, Uri uri, byte[] imageBytes, - Resources res, int resId, int rotation, boolean leftAligned) { - int width = size.x; - int height = size.y; - - BitmapCropTask cropTask; - if (uri != null) { - cropTask = new BitmapCropTask( - context, uri, null, rotation, width, height, false, true, null); - } else if (imageBytes != null) { - cropTask = new BitmapCropTask( - imageBytes, null, rotation, width, height, false, true, null); - } else { - cropTask = new BitmapCropTask( - context, res, resId, null, rotation, width, height, false, true, null); - } - Point bounds = cropTask.getImageBounds(); - if (bounds == null || bounds.x == 0 || bounds.y == 0) { - return null; - } - - Matrix rotateMatrix = new Matrix(); - rotateMatrix.setRotate(rotation); - float[] rotatedBounds = new float[] { bounds.x, bounds.y }; - rotateMatrix.mapPoints(rotatedBounds); - rotatedBounds[0] = Math.abs(rotatedBounds[0]); - rotatedBounds[1] = Math.abs(rotatedBounds[1]); - - RectF cropRect = getMaxCropRect( - (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); - cropTask.setCropBounds(cropRect); - - if (cropTask.cropBitmap()) { - return cropTask.getCroppedBitmap(); - } else { - return null; - } - } - - /** - * getMaxCropRect from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static RectF getMaxCropRect( - int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { - RectF cropRect = new RectF(); - // Get a crop rect that will fit this - if (inWidth / (float) inHeight > outWidth / (float) outHeight) { - cropRect.top = 0; - cropRect.bottom = inHeight; - cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; - cropRect.right = inWidth - cropRect.left; - if (leftAligned) { - cropRect.right -= cropRect.left; - cropRect.left = 0; - } - } else { - cropRect.left = 0; - cropRect.right = inWidth; - cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; - cropRect.bottom = inHeight - cropRect.top; - } - return cropRect; - } - - /** - * convertExtensionToCompressFormat from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static Bitmap.CompressFormat convertExtensionToCompressFormat(String extension) { - return extension.equals("png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; - } - - /** - * getFileExtension from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static String getFileExtension(String requestFormat) { - String outputFormat = (requestFormat == null) - ? "jpg" - : requestFormat; - outputFormat = outputFormat.toLowerCase(); - return (outputFormat.equals("png") || outputFormat.equals("gif")) - ? "png" // We don't support gif compression. - : "jpg"; - } - - /** - * BitmapCropTask from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static class BitmapCropTask extends AsyncTask { - Uri mInUri = null; - Context mContext; - String mInFilePath; - byte[] mInImageBytes; - int mInResId = 0; - InputStream mInStream; - RectF mCropBounds = null; - int mOutWidth, mOutHeight; - int mRotation; - String mOutputFormat = "jpg"; // for now - boolean mSetWallpaper; - boolean mSaveCroppedBitmap; - Bitmap mCroppedBitmap; - Runnable mOnEndRunnable; - Resources mResources; - OnBitmapCroppedHandler mOnBitmapCroppedHandler; - boolean mNoCrop; - boolean mImageFromAsset; - - public BitmapCropTask(byte[] imageBytes, - RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mInImageBytes = imageBytes; - init(cropBounds, rotation, - outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); - } - - public BitmapCropTask(Context c, Uri inUri, - RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mContext = c; - mInUri = inUri; - init(cropBounds, rotation, - outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); - } - - public BitmapCropTask(Context c, Resources res, int inResId, - RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mContext = c; - mInResId = inResId; - mResources = res; - init(cropBounds, rotation, - outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); - } - - private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mCropBounds = cropBounds; - mRotation = rotation; - mOutWidth = outWidth; - mOutHeight = outHeight; - mSetWallpaper = setWallpaper; - mSaveCroppedBitmap = saveCroppedBitmap; - mOnEndRunnable = onEndRunnable; - } - - // Helper to setup input stream - private void regenerateInputStream() { - if (mInUri == null && mInResId == 0 && mInFilePath == null && - mInImageBytes == null && !mImageFromAsset) { - Log.w(TAG, "cannot read original file, no input URI, resource ID, or " + - "image byte array given"); - } else { - Utils.closeQuiet(mInStream); - try { - if (mInUri != null) { - mInStream = new BufferedInputStream( - mContext.getContentResolver().openInputStream(mInUri)); - } else if (mInFilePath != null) { - mInStream = mContext.openFileInput(mInFilePath); - } else if (mInImageBytes != null) { - mInStream = new BufferedInputStream( - new ByteArrayInputStream(mInImageBytes)); - } else { - mInStream = new BufferedInputStream( - mResources.openRawResource(mInResId)); - } - } catch (FileNotFoundException e) { - Log.w(TAG, "cannot read file: " + mInUri.toString(), e); - } - } - } - - public Point getImageBounds() { - regenerateInputStream(); - if (mInStream != null) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(mInStream, null, options); - if (options.outWidth != 0 && options.outHeight != 0) { - return new Point(options.outWidth, options.outHeight); - } - } - return null; - } - - public void setCropBounds(RectF cropBounds) { - mCropBounds = cropBounds; - } - - public Bitmap getCroppedBitmap() { - return mCroppedBitmap; - } - public boolean cropBitmap() { - boolean failure = false; - - regenerateInputStream(); - - WallpaperManager wallpaperManager = null; - if (mSetWallpaper) { - wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); - } - if (mSetWallpaper && mNoCrop && mInStream != null) { - try { - wallpaperManager.setStream(mInStream); - } catch (IOException e) { - Log.w(TAG, "cannot write stream to wallpaper", e); - failure = true; - } - return !failure; - } - if (mInStream != null) { - // Find crop bounds (scaled to original image size) - Rect roundedTrueCrop = new Rect(); - Matrix rotateMatrix = new Matrix(); - Matrix inverseRotateMatrix = new Matrix(); - if (mRotation > 0) { - rotateMatrix.setRotate(mRotation); - inverseRotateMatrix.setRotate(-mRotation); - - mCropBounds.roundOut(roundedTrueCrop); - mCropBounds = new RectF(roundedTrueCrop); - - Point bounds = getImageBounds(); - - float[] rotatedBounds = new float[] { bounds.x, bounds.y }; - rotateMatrix.mapPoints(rotatedBounds); - rotatedBounds[0] = Math.abs(rotatedBounds[0]); - rotatedBounds[1] = Math.abs(rotatedBounds[1]); - - mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); - inverseRotateMatrix.mapRect(mCropBounds); - mCropBounds.offset(bounds.x/2, bounds.y/2); - - regenerateInputStream(); - } - - mCropBounds.roundOut(roundedTrueCrop); - - if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { - Log.w(TAG, "crop has bad values for full size image"); - failure = true; - return false; - } - - // See how much we're reducing the size of the image - int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, - roundedTrueCrop.height() / mOutHeight); - - // Attempt to open a region decoder - BitmapRegionDecoder decoder = null; - try { - decoder = BitmapRegionDecoder.newInstance(mInStream, true); - } catch (IOException e) { - Log.w(TAG, "cannot open region decoder for file: " + mInUri.toString(), e); - } - - Bitmap crop = null; - if (decoder != null) { - // Do region decoding to get crop bitmap - BitmapFactory.Options options = new BitmapFactory.Options(); - if (scaleDownSampleSize > 1) { - options.inSampleSize = scaleDownSampleSize; - } - crop = decoder.decodeRegion(roundedTrueCrop, options); - decoder.recycle(); - } - - if (crop == null) { - // BitmapRegionDecoder has failed, try to crop in-memory - regenerateInputStream(); - Bitmap fullSize = null; - if (mInStream != null) { - BitmapFactory.Options options = new BitmapFactory.Options(); - if (scaleDownSampleSize > 1) { - options.inSampleSize = scaleDownSampleSize; - } - fullSize = BitmapFactory.decodeStream(mInStream, null, options); - } - if (fullSize != null) { - mCropBounds.left /= scaleDownSampleSize; - mCropBounds.top /= scaleDownSampleSize; - mCropBounds.bottom /= scaleDownSampleSize; - mCropBounds.right /= scaleDownSampleSize; - mCropBounds.roundOut(roundedTrueCrop); - - crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, - roundedTrueCrop.top, roundedTrueCrop.width(), - roundedTrueCrop.height()); - } - } - - if (crop == null) { - Log.w(TAG, "cannot decode file: " + mInUri.toString()); - failure = true; - return false; - } - if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { - float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; - rotateMatrix.mapPoints(dimsAfter); - dimsAfter[0] = Math.abs(dimsAfter[0]); - dimsAfter[1] = Math.abs(dimsAfter[1]); - - if (!(mOutWidth > 0 && mOutHeight > 0)) { - mOutWidth = Math.round(dimsAfter[0]); - mOutHeight = Math.round(dimsAfter[1]); - } - - RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); - RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); - - Matrix m = new Matrix(); - if (mRotation == 0) { - m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); - } else { - Matrix m1 = new Matrix(); - m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); - Matrix m2 = new Matrix(); - m2.setRotate(mRotation); - Matrix m3 = new Matrix(); - m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); - Matrix m4 = new Matrix(); - m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); - - Matrix c1 = new Matrix(); - c1.setConcat(m2, m1); - Matrix c2 = new Matrix(); - c2.setConcat(m4, m3); - m.setConcat(c2, c1); - } - - Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), - (int) returnRect.height(), Bitmap.Config.ARGB_8888); - if (tmp != null) { - Canvas c = new Canvas(tmp); - Paint p = new Paint(); - p.setFilterBitmap(true); - c.drawBitmap(crop, m, p); - crop = tmp; - } - } - - if (mSaveCroppedBitmap) { - mCroppedBitmap = crop; - } - - // Get output compression format - Bitmap.CompressFormat cf = - convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); - - // Compress to byte array - ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); - if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { - // If we need to set to the wallpaper, set it - if (mSetWallpaper && wallpaperManager != null) { - try { - byte[] outByteArray = tmpOut.toByteArray(); - wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); - if (mOnBitmapCroppedHandler != null) { - mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); - } - } catch (IOException e) { - Log.w(TAG, "cannot write stream to wallpaper", e); - failure = true; - } - } - } else { - Log.w(TAG, "cannot compress bitmap"); - failure = true; - } - } - return !failure; // True if any of the operations failed - } - - @Override - protected Boolean doInBackground(Void... params) { - return cropBitmap(); - } - - @Override - protected void onPostExecute(Boolean result) { - if (mOnEndRunnable != null) { - mOnEndRunnable.run(); - } - } - - public interface OnBitmapCroppedHandler { - public void onBitmapCropped(byte[] imageBytes); - } - } -} diff --git a/src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java b/src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java deleted file mode 100644 index e034357..0000000 --- a/src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; - -public class AutoSnapHorizontalScrollView extends HorizontalScrollView { - private static final int SNAP_ON_UP_DELAY = 250; - - private int mScrollPositionOnUp; - - enum EventStates { - SCROLLING, - FLING - } - - private EventStates mSystemState = EventStates.SCROLLING; - - public AutoSnapHorizontalScrollView(Context context) { - super(context); - } - - public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - Runnable mSnapRunnable = new Runnable(){ - @Override - public void run() { - snapItems(); - mSystemState = EventStates.SCROLLING; - } - }; - - /** - * Added runnable for snapping on an item when the user lifts up their finger and - * there is no scrolling taking place (i.e. no flinging) - */ - Runnable mSnapOnUpRunnable = new Runnable(){ - @Override - public void run() { - int scrollX = getScrollX(); - if (scrollX != mScrollPositionOnUp) { - mScrollPositionOnUp = scrollX; - postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); - } else { - snapItems(); - mSystemState = EventStates.SCROLLING; - } - } - }; - - @Override - public boolean onTouchEvent(MotionEvent ev) { - int action = ev.getAction(); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mSystemState = EventStates.FLING; - removeCallbacks(mSnapRunnable); - mScrollPositionOnUp = getScrollX(); - postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); - } else if (action == MotionEvent.ACTION_DOWN) { - mSystemState = EventStates.SCROLLING; - removeCallbacks(mSnapRunnable); - removeCallbacks(mSnapOnUpRunnable); - } - return super.onTouchEvent(ev); - } - - private void snapItems() { - Rect parentBounds = new Rect(); - getDrawingRect(parentBounds); - Rect childBounds = new Rect(); - ViewGroup parent = (ViewGroup) getChildAt(0); - for (int i = 0; i < parent.getChildCount(); i++) { - View view = parent.getChildAt(i); - view.getHitRect(childBounds); - if (childBounds.right >= parentBounds.left && childBounds.left <= parentBounds.left) { - // First partially visible child - if ((childBounds.right - parentBounds.left) >= - (parentBounds.left - childBounds.left)) { - smoothScrollTo(Math.abs(childBounds.left), 0); - } else { - /** - * Added code to take into account dividers so that we do not see - * one on the edge of the screen when items snap in place. - */ - int dividerWidth = 0; - if (parent instanceof LinearLayout) { - dividerWidth = ((LinearLayout) parent).getDividerWidth(); - } - smoothScrollTo(Math.abs(childBounds.right) + dividerWidth, 0); - } - break; - } - } - } - - // Overwrite measureChildX as we want our child to be able to tell the - // parents width but do not impose any limits (AT_MOST; AT_MAX) - @Override - protected void measureChild(View child, int parentWidthMeasureSpec, - int parentHeightMeasureSpec) { - ViewGroup.LayoutParams lp = child.getLayoutParams(); - - int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop - + mPaddingBottom, lp.height); - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parentWidthMeasureSpec) - (mPaddingLeft + mPaddingRight), - MeasureSpec.UNSPECIFIED); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - @Override - protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, - int parentHeightMeasureSpec, int heightUsed) { - MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); - - int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, - mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin - + heightUsed, lp.height); - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parentWidthMeasureSpec) - - (mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin - + widthUsed), MeasureSpec.UNSPECIFIED); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - if (mSystemState == EventStates.SCROLLING) { - return; - } - if (Math.abs(l - oldl) <= 1 && mSystemState == EventStates.FLING) { - removeCallbacks(mSnapRunnable); - postDelayed(mSnapRunnable, 100); - } - } -} diff --git a/src/com/cyngn/theme/widget/BootAniImageView.java b/src/com/cyngn/theme/widget/BootAniImageView.java deleted file mode 100644 index 44363d6..0000000 --- a/src/com/cyngn/theme/widget/BootAniImageView.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.ImageView; -import libcore.io.IoUtils; -import com.cyngn.theme.util.BootAnimationHelper; - -import java.io.IOException; -import java.util.List; -import java.util.zip.ZipFile; - -public class BootAniImageView extends ImageView { - private static final String TAG = BootAniImageView.class.getName(); - - private static final boolean DEBUG = false; - - private static final int MAX_BUFFERS = 2; - - private Bitmap[] mBuffers = new Bitmap[MAX_BUFFERS]; - private int mReadBufferIndex = 0; - private int mWriteBufferIndex = 0; - private ZipFile mBootAniZip; - - private List mAnimationParts; - private int mCurrentPart; - private int mCurrentFrame; - private int mCurrentPartPlayCount; - private int mFrameDuration; - - private boolean mActive = false; - - public BootAniImageView(Context context) { - this(context, null); - } - - public BootAniImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BootAniImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - if (visibility == VISIBLE) { - if (mBootAniZip != null) start(); - } else { - stop(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - // In case we end up in the mid dle of onDraw while the buffers are being recycled - // we catch the exception and just let frame not be rendered. - try { - super.onDraw(canvas); - } catch (RuntimeException e) { - Log.e(TAG, "Unable to draw boot animation frame."); - } - } - - public synchronized boolean setBootAnimation(ZipFile bootAni) { - if (bootAni == null) { - Log.w(TAG, "Boot animation ZipFile is null."); - return false; - } - // make sure we are stopped first - stop(); - - if (mBootAniZip != null) { - IoUtils.closeQuietly(mBootAniZip); - // This boot animation may be a different size than the previous - // one so clear out the buffers so they can be recreated with - // the correct size. - for (int i = 0; i < MAX_BUFFERS; i++) { - if (mBuffers[i] != null) { - mBuffers[i].recycle(); - mBuffers[i] = null; - } - } - } - mBootAniZip = bootAni; - - try { - mAnimationParts = BootAnimationHelper.parseAnimation(mBootAniZip); - } catch (Exception e) { - // "Gotta catch 'em all" - Log.e(TAG, "Unable to set boot animation", e); - mAnimationParts = null; - mBootAniZip = null; - return false; - } - - if (mAnimationParts == null || mAnimationParts.size() == 0) { - return false; - } - - final BootAnimationHelper.AnimationPart part = mAnimationParts.get(0); - mCurrentPart = 0; - mCurrentPartPlayCount = part.playCount; - mFrameDuration = part.frameRateMillis; - mWriteBufferIndex = mReadBufferIndex = 0; - mCurrentFrame = 0; - - getNextFrame(); - - return true; - } - - private void getNextFrame() { - if (mAnimationParts == null || mAnimationParts.size() == 0) return; - - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inBitmap = mBuffers[mWriteBufferIndex]; - opts.inPreferredConfig = Bitmap.Config.RGB_565; - opts.inMutable = true; - final BootAnimationHelper.AnimationPart part = mAnimationParts.get(mCurrentPart); - try { - mBuffers[mWriteBufferIndex] = - BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( - part.frames.get(mCurrentFrame++))), null, opts); - } catch (IllegalArgumentException iae) { - // In case we're here because the bitmap could not be re-used, try creating a new one - opts.inBitmap = null; - try { - if (DEBUG) { - Log.d(TAG, "Trying to load frame without reusing existing bitmap", iae); - } - if (mBuffers[mWriteBufferIndex] != null) { - // clean up our old bitmap - mBuffers[mWriteBufferIndex].recycle(); - mBuffers[mWriteBufferIndex] = null; - } - mBuffers[mWriteBufferIndex] = - BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( - part.frames.get(mCurrentFrame++))), null, opts); - } catch (Exception e) { - // Still failling? Let's log it and carry on. - Log.w(TAG, "Unable to get next frame", e); - } - } catch (IOException e) { - Log.w(TAG, "Unable to get next frame", e); - } - mWriteBufferIndex = (mWriteBufferIndex + 1) % MAX_BUFFERS; - if (mCurrentFrame >= part.frames.size()) { - if (mCurrentPartPlayCount > 0) { - if (--mCurrentPartPlayCount == 0) { - mCurrentPart++; - if (mCurrentPart >= mAnimationParts.size()) mCurrentPart = 0; - mCurrentFrame = 0; - mCurrentPartPlayCount = mAnimationParts.get(mCurrentPart).playCount; - } else { - mCurrentFrame = 0; - } - } else { - mCurrentFrame = 0; - } - } - } - - public void start() { - if (mAnimationParts == null) return; - - mActive = true; - post(mUpdateAnimationRunnable); - } - - public void stop() { - mActive = false; - removeCallbacks(mUpdateImageRunnable); - removeCallbacks(mUpdateAnimationRunnable); - } - - private Runnable mUpdateAnimationRunnable = new Runnable() { - @Override - public void run() { - if (!mActive) return; - BootAniImageView.this.postDelayed(mUpdateAnimationRunnable, mFrameDuration); - if (!isOffScreen()) { - BootAniImageView.this.post(mUpdateImageRunnable); - mReadBufferIndex = (mReadBufferIndex + 1) % MAX_BUFFERS; - getNextFrame(); - } - } - }; - - private Runnable mUpdateImageRunnable = new Runnable() { - @Override - public void run() { - setImageBitmap(mBuffers[mReadBufferIndex]); - } - }; - - private boolean isOffScreen() { - int[] pos = new int[2]; - getLocationOnScreen(pos); - return pos[1] >= mContext.getResources().getDisplayMetrics().heightPixels; - } -} diff --git a/src/com/cyngn/theme/widget/ConfirmCancelOverlay.java b/src/com/cyngn/theme/widget/ConfirmCancelOverlay.java deleted file mode 100644 index 54fffa1..0000000 --- a/src/com/cyngn/theme/widget/ConfirmCancelOverlay.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.TextView; -import com.cyngn.theme.chooser.R; - -public class ConfirmCancelOverlay extends FrameLayout { - - private View mAcceptButton; - private View mCancelButton; - private TextView mTitle; - - private OnOverlayDismissedListener mListener; - - public ConfirmCancelOverlay(Context context) { - this(context, null); - } - - public ConfirmCancelOverlay(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ConfirmCancelOverlay(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mAcceptButton = findViewById(R.id.accept); - mCancelButton = findViewById(R.id.cancel); - mTitle = (TextView) findViewById(R.id.overlay_title); - - mAcceptButton.setOnClickListener(mClickListener); - mCancelButton.setOnClickListener(mClickListener); - } - - public void setTitle(CharSequence title) { - mTitle.setText(title); - } - - public void setTitle(int resId) { - mTitle.setText(resId); - } - - public void setOnOverlayDismissedListener(OnOverlayDismissedListener listener) { - mListener = listener; - } - - public void dismiss() { - if (mListener != null) { - mListener.onDismissed(false); - } - } - - private OnClickListener mClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onDismissed(v == mAcceptButton); - } - } - }; - - public interface OnOverlayDismissedListener { - public void onDismissed(boolean accepted); - } -} diff --git a/src/com/cyngn/theme/widget/FittedTextView.java b/src/com/cyngn/theme/widget/FittedTextView.java deleted file mode 100644 index 2406a59..0000000 --- a/src/com/cyngn/theme/widget/FittedTextView.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.graphics.Paint; -import android.text.method.TransformationMethod; -import android.text.method.AllCapsTransformationMethod; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.widget.TextView; - -/** - * Change the font size to match the measured - * textview size by width - * - */ -public class FittedTextView extends TextView { - private Paint mPaint; - //If set to true, the text will be resized to fit the view. - private boolean mAutoFitText = true; - //Used to instruct whether the text should be expanded to fill out the view, even if the text - //fits without being resized - private boolean mAutoExpand = true; - - public FittedTextView(Context context) { - this(context, null); - } - - public FittedTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public FittedTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mPaint = new Paint(); - } - - protected void setAutoFitText(boolean autoFit) { - mAutoFitText = autoFit; - } - - protected boolean getAutoFitText() { - return mAutoFitText; - } - - protected void setAutoExpand(boolean autoExpand) { - mAutoExpand = autoExpand; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (!mAutoFitText) return; - - final float THRESHOLD = 0.5f; - final float TARGET_WIDTH = getMeasuredWidth(); - String text = getText().toString(); - TransformationMethod tm = getTransformationMethod(); - if (tm != null && tm instanceof AllCapsTransformationMethod) { - text = getText().toString().toUpperCase(); - } - mPaint.set(getPaint()); - - if (mPaint.measureText(text) <= TARGET_WIDTH && !mAutoExpand) return; - - float max = 200; - float min = 2; - while(max > min) { - float size = (max+min) / 2; - mPaint.setTextSize(size); - float measuredWidth = mPaint.measureText(text); - if (Math.abs(TARGET_WIDTH - measuredWidth) <= THRESHOLD) { - break; - } else if (measuredWidth > TARGET_WIDTH) { - max = size-1; - } else { - min = size+1; - } - } - this.setTextSize(TypedValue.COMPLEX_UNIT_PX, min-1); - } -} diff --git a/src/com/cyngn/theme/widget/LatoTextView.java b/src/com/cyngn/theme/widget/LatoTextView.java deleted file mode 100644 index 9ced870..0000000 --- a/src/com/cyngn/theme/widget/LatoTextView.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Typeface; -import android.util.AttributeSet; -import android.widget.TextView; -import com.cyngn.theme.chooser.R; -import com.cyngn.theme.util.Utils; - -import java.io.File; - -/** - * A custom TextView that always uses the Lato font - */ -public class LatoTextView extends FittedTextView { - private static final int NUM_TYPEFACE_PER_FAMILY = 4; - - private static final String FONT_ASSSET_DIR = "fonts"; - // Regular fonts - private static final String LATO_REGULAR_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-Regular.ttf"; - private static final String LATO_REGULAR_BOLD_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-RegBold.ttf"; - private static final String LATO_REGULAR_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-RegItalic.otf"; - private static final String LATO_REGULAR_BOLD_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-RegBoldItalic.ttf"; - // Condensed fonts - private static final String LATO_CONDENSED_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-Cond.ttf"; - private static final String LATO_CONDENSED_BOLD_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-CondBold.ttf"; - private static final String LATO_CONDENSED_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-CondItalic.ttf"; - private static final String LATO_CONDENSED_BOLD_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-CondBoldItalic.ttf"; - // Light fonts - private static final String LATO_LIGHT_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-Light.ttf"; - private static final String LATO_LIGHT_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-LightItalic.ttf"; - - private static final String CONDENSED = "condensed"; - private static final String LIGHT = "light"; - - private static Typeface[] sLatoRegularTypeface; - private static Typeface[] sLatoCondensedTypeface; - private static Typeface[] sLatoLightTypeface; - private static final Object sLock = new Object(); - - // Retrieving these attributes is done via reflection so let's just do this once and share - // it amongst the other instances of LatoTextView - private static int[] sTextViewStyleAttributes; - - public LatoTextView(Context context) { - this(context, null); - } - - public LatoTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public LatoTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - synchronized (sLock) { - AssetManager assets = context.getAssets(); - if (sLatoRegularTypeface == null) { - sLatoRegularTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; - sLatoRegularTypeface[Typeface.NORMAL] = - Typeface.createFromAsset(assets, LATO_REGULAR_PATH); - sLatoRegularTypeface[Typeface.BOLD] = - Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_PATH); - sLatoRegularTypeface[Typeface.ITALIC] = - Typeface.createFromAsset(assets, LATO_REGULAR_ITALIC_PATH); - sLatoRegularTypeface[Typeface.BOLD_ITALIC] = - Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_ITALIC_PATH); - } - if (sLatoCondensedTypeface == null) { - sLatoCondensedTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; - sLatoCondensedTypeface[Typeface.NORMAL] = - Typeface.createFromAsset(assets, LATO_CONDENSED_PATH); - sLatoCondensedTypeface[Typeface.BOLD] = - Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_PATH); - sLatoCondensedTypeface[Typeface.ITALIC] = - Typeface.createFromAsset(assets, LATO_CONDENSED_ITALIC_PATH); - sLatoCondensedTypeface[Typeface.BOLD_ITALIC] = - Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_ITALIC_PATH); - } - if (sLatoLightTypeface == null) { - sLatoLightTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; - sLatoLightTypeface[Typeface.NORMAL] = - Typeface.createFromAsset(assets, LATO_LIGHT_PATH); - sLatoLightTypeface[Typeface.BOLD] = - sLatoRegularTypeface[Typeface.BOLD]; - sLatoLightTypeface[Typeface.ITALIC] = - Typeface.createFromAsset(assets, LATO_LIGHT_ITALIC_PATH); - sLatoLightTypeface[Typeface.BOLD_ITALIC] = - sLatoRegularTypeface[Typeface.BOLD_ITALIC]; - } - } - - final Resources.Theme theme = context.getTheme(); - if (sTextViewStyleAttributes == null) { - sTextViewStyleAttributes = - Utils.getResourceDeclareStyleableIntArray("com.android.internal", "TextView"); - } - - if (sTextViewStyleAttributes != null) { - TypedArray a = - theme.obtainStyledAttributes(attrs, sTextViewStyleAttributes, defStyle, 0); - String fontFamily = "sans-serif"; - int styleIndex = Typeface.NORMAL; - if (a != null) { - int n = a.getIndexCount(); - for (int i = 0; i < n; i++) { - int attr = a.getIndex(i); - - final Resources res = getResources(); - int attrFontFamily = - res.getIdentifier("TextView_fontFamily", "styleable", "android"); - int attrTextStyle = - res.getIdentifier("TextView_textStyle", "styleable", "android"); - if (attr == attrFontFamily) { - fontFamily = a.getString(attr); - } else if (attr == attrTextStyle) { - styleIndex = a.getInt(attr, styleIndex); - } - } - a.recycle(); - } - - setTypefaceFromAttrs(fontFamily, styleIndex); - setAutoExpand(false); - TypedArray styledAttrs = context.obtainStyledAttributes(attrs, - R.styleable.FittedTextView, 0, 0); - try { - //Although we extend FittedTextView, we don't want all instances to auto fit the - //text, so we check if autoFitText has been set in the attributes. Default to false - boolean fit = styledAttrs.getBoolean(R.styleable.FittedTextView_autoFitText, false); - setAutoFitText(fit); - } finally { - styledAttrs.recycle(); - } - } - } - - private void setTypefaceFromAttrs(String familyName, int styleIndex) { - Typeface tf = null; - if (familyName != null) { - Typeface[] typefaces = sLatoRegularTypeface; - if (familyName.contains(CONDENSED)) { - typefaces = sLatoCondensedTypeface; - } else if (familyName.contains(LIGHT)) { - typefaces = sLatoLightTypeface; - } - tf = typefaces[styleIndex]; - if (tf != null) { - setTypeface(tf); - return; - } - } - setTypeface(tf, styleIndex); - } -} diff --git a/src/com/cyngn/theme/widget/LockableScrollView.java b/src/com/cyngn/theme/widget/LockableScrollView.java deleted file mode 100644 index 88f484a..0000000 --- a/src/com/cyngn/theme/widget/LockableScrollView.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.ScrollView; - -public class LockableScrollView extends ScrollView { - private boolean mScrollingEnabled = true; - - public LockableScrollView(Context context) { - this(context, null); - } - - public LockableScrollView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public LockableScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void setScrollingEnabled(boolean enabled) { - mScrollingEnabled = enabled; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return mScrollingEnabled && super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - return mScrollingEnabled && super.onTouchEvent(ev); - default: - return super.onTouchEvent(ev); - } - } - - @Override - public void setOverScrollMode(int mode) { - // Some themes can cause theme chooser to crash when creating the EdgeEffects for - // the scroll view. If an exception occurs we fallback to no overscroll - try { - super.setOverScrollMode(mode); - } catch (Exception e) { - super.setOverScrollMode(OVER_SCROLL_NEVER); - } - } -} diff --git a/src/com/cyngn/theme/widget/NavBarSpace.java b/src/com/cyngn/theme/widget/NavBarSpace.java deleted file mode 100644 index 993e9cf..0000000 --- a/src/com/cyngn/theme/widget/NavBarSpace.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import com.cyngn.theme.util.Utils; - -/** - * A simple view used to pad layouts so that content floats above the - * navigation bar. This is best used with transparent or translucent - * navigation bars where the content can go behind them. - */ -public class NavBarSpace extends View { - - public NavBarSpace(Context context) { - this(context, null); - } - - public NavBarSpace(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public NavBarSpace(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - if (!Utils.hasNavigationBar(getContext())) { - this.setVisibility(View.GONE); - } else { - this.setVisibility(View.VISIBLE); - } - } -} diff --git a/src/com/cyngn/theme/widget/ThemeTagLayout.java b/src/com/cyngn/theme/widget/ThemeTagLayout.java deleted file mode 100644 index 18635a4..0000000 --- a/src/com/cyngn/theme/widget/ThemeTagLayout.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import com.cyngn.theme.chooser.R; - -public class ThemeTagLayout extends LinearLayout { - private ImageView mAppliedTag; - private TextView mCustomizedTag; - private TextView mUpdatedTag; - private TextView mDefaultTag; - private TextView mLegacyTag; - - public ThemeTagLayout(Context context) { - this(context, null); - } - - public ThemeTagLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ThemeTagLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - LayoutInflater inflater = - (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mAppliedTag = (ImageView) inflater.inflate(R.layout.tag_applied, this, false); - mCustomizedTag = (TextView) inflater.inflate(R.layout.tag_customized, this, false); - mUpdatedTag = (TextView) inflater.inflate(R.layout.tag_updated, this, false); - mDefaultTag = (TextView) inflater.inflate(R.layout.tag_default, this, false); - mLegacyTag = (TextView) inflater.inflate(R.layout.tag_legacy, this, false); - } - - public void setAppliedTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_applied) != null) return; - addView(mAppliedTag, 0); - } else { - if (findViewById(R.id.tag_applied) == null) return; - removeView(mAppliedTag); - } - } - - public void setCustomizedTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_customized) != null) return; - final int childCount = getChildCount(); - if (childCount > 1) { - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child != mAppliedTag) { - addView(mCustomizedTag, i); - break; - } - } - } else { - addView(mCustomizedTag); - } - } else { - if (findViewById(R.id.tag_customized) == null) return; - removeView(mCustomizedTag); - } - } - - public boolean isCustomizedTagEnabled() { - return findViewById(R.id.tag_customized) != null; - } - - public void setUpdatedTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_updated) != null) return; - final int childCount = getChildCount(); - if (childCount > 2) { - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child != mAppliedTag && child != mCustomizedTag) { - addView(mUpdatedTag, i); - break; - } - } - } else { - addView(mUpdatedTag); - } - } else { - if (findViewById(R.id.tag_updated) == null) return; - removeView(mUpdatedTag); - } - } - - public boolean isUpdatedTagEnabled() { - return findViewById(R.id.tag_updated) != null; - } - - public void setDefaultTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_default) != null) return; - addView(mDefaultTag); - } else { - if (findViewById(R.id.tag_default) == null) return; - removeView(mDefaultTag); - } - } - - public void setLegacyTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_legacy) != null) return; - addView(mLegacyTag); - } else { - if (findViewById(R.id.tag_legacy) == null) return; - removeView(mLegacyTag); - } - } -} diff --git a/src/com/viewpagerindicator/CirclePageIndicator.java b/src/com/viewpagerindicator/CirclePageIndicator.java index 81365af..19b031e 100644 --- a/src/com/viewpagerindicator/CirclePageIndicator.java +++ b/src/com/viewpagerindicator/CirclePageIndicator.java @@ -34,7 +34,7 @@ import android.view.View; import android.view.ViewConfiguration; import au.com.glassechidna.velocityviewpager.VelocityViewPager; -import com.cyngn.theme.chooser.R; +import org.cyanogenmod.theme.chooser.R; import static android.graphics.Paint.ANTI_ALIAS_FLAG; import static android.widget.LinearLayout.HORIZONTAL; diff --git a/src/org/cyanogenmod/theme/chooser/AppReceiver.java b/src/org/cyanogenmod/theme/chooser/AppReceiver.java new file mode 100644 index 0000000..0c3037e --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/AppReceiver.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.text.TextUtils; + +import org.cyanogenmod.theme.util.NotificationHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract; + +public class AppReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Uri uri = intent.getData(); + String pkgName = uri != null ? uri.getSchemeSpecificPart() : null; + String action = intent.getAction(); + + if (cyanogenmod.content.Intent.ACTION_THEME_INSTALLED.equals(action)) { + if (!pkgName.equals(Utils.getDefaultThemePackageName(context))) { + NotificationHelper.postThemeInstalledNotification(context, pkgName); + } + } else if (cyanogenmod.content.Intent.ACTION_THEME_REMOVED.equals(action)) { + // remove updated status for this theme (if one exists) + PreferenceUtils.removeUpdatedTheme(context, pkgName); + + // If the theme being removed was the currently applied theme we need + // to update the applied base theme in preferences to the default theme. + String appliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(context); + if (!TextUtils.isEmpty(appliedBaseTheme) && appliedBaseTheme.equals(pkgName)) { + PreferenceUtils.setAppliedBaseTheme(context, + Utils.getDefaultThemePackageName(context)); + } + NotificationHelper.cancelNotifications(context); + } else if (cyanogenmod.content.Intent.ACTION_THEME_UPDATED.equals(action)) { + try { + if (isTheme(context, pkgName)) { + PreferenceUtils.addUpdatedTheme(context, pkgName); + } + } catch (NameNotFoundException e) { + } + } + } + + private boolean isTheme(Context context, String pkgName) throws NameNotFoundException { + PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); + + return pi != null && pi.themeInfo != null; + } +} diff --git a/src/org/cyanogenmod/theme/chooser/BootReceiver.java b/src/org/cyanogenmod/theme/chooser/BootReceiver.java new file mode 100644 index 0000000..00a6723 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/BootReceiver.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +public class BootReceiver extends BroadcastReceiver { + private static final String CHOOSER_PKG_NAME = "org.cyanogenmod.theme.chooser"; + private static final String CHOOSER_ACTIVITY = "org.cyanogenmod.theme.chooser.ChooserLauncher"; + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + PackageManager pm = context.getPackageManager(); + if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + try { + PackageInfo info = pm.getPackageInfo(ChooserActivity.THEME_STORE_PACKAGE, 0); + if (info != null) { + ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, CHOOSER_ACTIVITY); + pm.setComponentEnabledSetting(cn, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } + } catch (PackageManager.NameNotFoundException e) { + // no store so nothing to do. + } + + // now disable this receiver so we don't get called on future boots + ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, + BootReceiver.class.getCanonicalName()); + pm.setComponentEnabledSetting(cn, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/ChooserActivity.java b/src/org/cyanogenmod/theme/chooser/ChooserActivity.java new file mode 100644 index 0000000..081325d --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ChooserActivity.java @@ -0,0 +1,1191 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.provider.Settings; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v4.view.ThemeViewPager; +import android.support.v4.view.ViewPager; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.MutableLong; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.Toast; + +import org.cyanogenmod.theme.perapptheming.PerAppThemingWindow; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.NotificationHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.platform.Manifest; +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static org.cyanogenmod.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_INSTALLED_THEMES; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_APPLIED; + +public class ChooserActivity extends FragmentActivity + implements LoaderManager.LoaderCallbacks { + public static final String THEME_STORE_PACKAGE = "com.cyngn.theme.tore"; + private static final String TAG = ChooserActivity.class.getSimpleName(); + + public static final String DEFAULT = ThemeConfig.SYSTEM_DEFAULT; + public static final String EXTRA_PKGNAME = "pkgName"; + public static final String EXTRA_COMPONENTS = "components"; + + private static final int OFFSCREEN_PAGE_LIMIT = 3; + + private static final String THEME_STORE_ACTIVITY = THEME_STORE_PACKAGE + ".ui.StoreActivity"; + private static final String ACTION_APPLY_THEME = "android.intent.action.APPLY_THEME"; + + private static final String TYPE_IMAGE = "image/*"; + + private static final String ACTION_PICK_LOCK_SCREEN_WALLPAPER = + "com.cyngn.intent.action.PICK_LOCK_SCREEN_WALLPAPER"; + + /** + * Request code for picking an external wallpaper + */ + public static final int REQUEST_PICK_WALLPAPER_IMAGE = 2; + /** + * Request code for picking an external lockscreen wallpaper + */ + public static final int REQUEST_PICK_LOCKSCREEN_IMAGE = 3; + + /** + * Request code for enabling system alert window permission + */ + private static final int REQUEST_SYSTEM_WINDOW_PERMISSION = 4; + + private static final long ANIMATE_CONTENT_IN_SCALE_DURATION = 500; + private static final long ANIMATE_CONTENT_IN_ALPHA_DURATION = 750; + private static final long ANIMATE_CONTENT_IN_BLUR_DURATION = 250; + private static final long ANIMATE_CONTENT_DELAY = 250; + private static final long ANIMATE_SHOP_THEMES_HIDE_DURATION = 250; + private static final long ANIMATE_SHOP_THEMES_SHOW_DURATION = 500; + private static final long FINISH_ANIMATION_DELAY = ThemeFragment.ANIMATE_DURATION + + ThemeFragment.ANIMATE_START_DELAY + 250; + + private static final long ANIMATE_CARDS_IN_DURATION = 250; + private static final long ANIMATE_SAVE_APPLY_LAYOUT_DURATION = 300; + private static final float ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR = 3; + private static final long ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY = 400; + + private PagerContainer mContainer; + private ThemeViewPager mPager; + + private ThemesAdapter mAdapter; + private boolean mExpanded = false; + private ComponentSelector mSelector; + private View mSaveApplyLayout; + private int mContainerYOffset = 0; + private TypefaceHelperCache mTypefaceHelperCache; + private boolean mIsAnimating; + private Handler mHandler; + private View mBottomActionsLayout; + + private String mSelectedTheme; + private String mAppliedBaseTheme; + private boolean mThemeChanging = false; + private boolean mAnimateContentIn = false; + private long mAnimateContentInDelay; + private String mThemeToApply; + private ArrayList mComponentsToApply; + + ImageView mCustomBackground; + + // Current system theme configuration as component -> pkgName + private Map mCurrentTheme = new HashMap(); + private MutableLong mCurrentWallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); + + private boolean mIsPickingImage = false; + private boolean mRestartLoaderOnCollapse = false; + private boolean mActivityResuming = false; + private boolean mShowLockScreenWallpaper = false; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + NotificationHijackingService.ensureEnabled(this); + + if (savedInstanceState == null) { + handleIntent(getIntent()); + } + + mContainer = (PagerContainer) findViewById(R.id.pager_container); + mPager = (ThemeViewPager) findViewById(R.id.viewpager); + + mPager.setOnClickListener(mPagerClickListener); + mAdapter = new ThemesAdapter(); + mPager.setAdapter(mAdapter); + + DisplayMetrics dm = getResources().getDisplayMetrics(); + int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, dm); + mPager.setPageMargin(-margin / 2); + mPager.setOffscreenPageLimit(OFFSCREEN_PAGE_LIMIT); + + mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + public void onPageSelected(int position) { + } + + public void onPageScrolled(int position, + float positionOffset, + int positionOffsetPixels) { + } + + public void onPageScrollStateChanged(int state) { + } + }); + + mSelector = (ComponentSelector) findViewById(R.id.component_selector); + mSelector.setOnOpenCloseListener(mOpenCloseListener); + + mBottomActionsLayout = findViewById(R.id.bottom_actions_layout); + + mSaveApplyLayout = findViewById(R.id.save_apply_layout); + mSaveApplyLayout.findViewById(R.id.save_apply_button).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mIsAnimating) return; + hideSaveApplyButton(); + mContainer.setClickable(false); + final ThemeFragment f = getCurrentFragment(); + if (mSelector.isEnabled()) { + mSelector.hide(); + if (mContainerYOffset != 0) { + slideContentBack(-mContainerYOffset); + mContainerYOffset = 0; + } + if (f != null) f.fadeInCards(); + if (mShowLockScreenWallpaper) { + mShowLockScreenWallpaper = false; + mSelector.resetComponentType(); + } + } + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + collapse(true); + } + }, ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY); + } + }); + + mBottomActionsLayout.findViewById(R.id.shop_themes) + .setOnClickListener(mOnShopThemesClicked); + + mTypefaceHelperCache = TypefaceHelperCache.getInstance(); + mHandler = new Handler(); + mCustomBackground = (ImageView) findViewById(R.id.custom_bg); + mAnimateContentIn = true; + mAnimateContentInDelay = 0; + + mBottomActionsLayout.findViewById(R.id.per_app_theming).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (Settings.canDrawOverlays(ChooserActivity.this)) { + launchAppThemer(); + } else { + requestSystemWindowPermission(); + } + } + }); + + if (PreferenceUtils.getShowPerAppThemeNewTag(this)) { + View tag = mBottomActionsLayout.findViewById(R.id.new_tag); + if (tag != null) { + tag.setVisibility(View.VISIBLE); + } + } + } + + public void showSaveApplyButton() { + if (mSaveApplyLayout != null && mSaveApplyLayout.getVisibility() != View.VISIBLE) { + mHandler.post(new Runnable() { + @Override + public void run() { + int navBarHeight = 0; + if (Utils.hasNavigationBar(ChooserActivity.this.getApplicationContext())) { + navBarHeight = ChooserActivity.this.getResources() + .getDimensionPixelSize(R.dimen.navigation_bar_height); + } + mSaveApplyLayout.setTranslationY(mSaveApplyLayout.getMeasuredHeight()); + mSaveApplyLayout.setVisibility(View.VISIBLE); + mSaveApplyLayout.animate() + .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION) + .setInterpolator( + new DecelerateInterpolator( + ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) + .translationY(-mSelector.getMeasuredHeight() + + navBarHeight); + } + }); + } + } + + public void hideSaveApplyButton() { + if (mSaveApplyLayout.getVisibility() != View.GONE) { + Animation anim = AnimationUtils.loadAnimation(this, + R.anim.component_selection_animate_out); + mSaveApplyLayout.startAnimation(anim); + anim.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + mSaveApplyLayout.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + } + } + + private void hideBottomActionsLayout() { + final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); + anim.alpha(0f).setDuration(ANIMATE_SHOP_THEMES_HIDE_DURATION); + anim.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mBottomActionsLayout.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + + private void showBottomActionsLayout() { + mBottomActionsLayout.setVisibility(View.VISIBLE); + final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); + anim.setListener(null); + anim.alpha(1f).setStartDelay(ThemeFragment.ANIMATE_DURATION) + .setDuration(ANIMATE_SHOP_THEMES_SHOW_DURATION); + } + + private void lauchGetThemes() { + String playStoreUrl = getString(R.string.play_store_url); + String wikiUrl = getString(R.string.wiki_url); + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(playStoreUrl)); + + // Try to launch play store + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } else { + // If no play store, try to open wiki url + intent.setData(Uri.parse(wikiUrl)); + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } else { + Toast.makeText(this, R.string.get_more_app_not_available, + Toast.LENGTH_LONG).show(); + } + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + if (Utils.isRecentTaskHome(this)) { + mContainer.setAlpha(0f); + mBottomActionsLayout.setAlpha(0f); + mAnimateContentIn = true; + mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; + } + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + String action = intent.getAction(); + if ((Intent.ACTION_MAIN.equals(action) || ACTION_APPLY_THEME.equals(action)) + && intent.hasExtra(EXTRA_PKGNAME)) { + if (intent.hasExtra(EXTRA_COMPONENTS)) { + mComponentsToApply = intent.getStringArrayListExtra(EXTRA_COMPONENTS); + } else { + mComponentsToApply = null; + } + mSelectedTheme = mComponentsToApply != null ? + PreferenceUtils.getAppliedBaseTheme(this) : + getSelectedTheme(intent.getStringExtra(EXTRA_PKGNAME)); + if (mPager != null) { + startLoader(LOADER_ID_INSTALLED_THEMES); + if (mExpanded) { + int collapseDelay = ThemeFragment.ANIMATE_START_DELAY; + if (mSelector.isEnabled()) { + // onBackPressed() has all the necessary logic for collapsing the + // component selector, so we call it here. + onBackPressed(); + collapseDelay += ThemeFragment.ANIMATE_DURATION; + } + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + collapse(false); + } + }, collapseDelay); + } + } + + if (ACTION_APPLY_THEME.equals(action) && + getCallingPackage() != null && + PackageManager.PERMISSION_GRANTED == + getPackageManager() + .checkPermission(Manifest.permission.WRITE_THEMES, + getCallingPackage())) { + mThemeToApply = intent.getStringExtra(EXTRA_PKGNAME); + } + } else if (ACTION_PICK_LOCK_SCREEN_WALLPAPER.equals(action)) { + mShowLockScreenWallpaper = true; + } + } + + private String getSelectedTheme(String requestedTheme) { + String[] projection = { ThemesColumns.PRESENT_AS_THEME }; + String selection = ThemesColumns.PKG_NAME + "=?"; + String[] selectionArgs = { requestedTheme }; + + String selectedTheme = PreferenceUtils.getAppliedBaseTheme(this); + + Cursor cursor = getContentResolver().query(ThemesColumns.CONTENT_URI, + projection, selection, selectionArgs, null); + if (cursor != null) { + if (cursor.getCount() > 0 && cursor.moveToFirst()) { + if (cursor.getInt(0) == 1) { + selectedTheme = requestedTheme; + } + } + cursor.close(); + } + return selectedTheme; + } + + private void setAnimatingStateAndScheduleFinish() { + mIsAnimating = true; + mContainer.setIsAnimating(true); + mHandler.postDelayed(new Runnable() { + public void run() { + mIsAnimating = false; + mContainer.setIsAnimating(false); + if (mRestartLoaderOnCollapse) { + mRestartLoaderOnCollapse = false; + startLoader(LOADER_ID_INSTALLED_THEMES); + } + } + }, FINISH_ANIMATION_DELAY); + if (mExpanded) { + hideBottomActionsLayout(); + } else { + showBottomActionsLayout(); + } + } + + private void setCustomBackground(final ImageView iv, final boolean animate) { + final Context context = ChooserActivity.this; + iv.post(new Runnable() { + @Override + public void run() { + Bitmap tmpBmp; + try { + tmpBmp = Utils.getRegularWallpaperBitmap(context); + } catch (Throwable e) { + Log.w(TAG, "Failed to retrieve wallpaper", e); + tmpBmp = null; + } + // Show the grid background if no wallpaper is set. + // Note: no wallpaper is actually a 1x1 pixel black bitmap + if (tmpBmp == null || tmpBmp.getWidth() <= 1 || tmpBmp.getHeight() <= 1) { + iv.setImageResource(R.drawable.bg_grid); + // We need to change the ScaleType to FIT_XY otherwise the background is cut + // off a bit at the bottom. + iv.setScaleType(ImageView.ScaleType.FIT_XY); + return; + } + + // Since we are applying a blur, we can afford to scale the bitmap down and use a + // smaller blur radius. + Bitmap inBmp = Bitmap.createScaledBitmap(tmpBmp, tmpBmp.getWidth() / 4, + tmpBmp.getHeight() / 4, false); + Bitmap outBmp = Bitmap.createBitmap(inBmp.getWidth(), inBmp.getHeight(), + Bitmap.Config.ARGB_8888); + + // Blur the original bitmap + RenderScript rs = RenderScript.create(context); + ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + Allocation tmpIn = Allocation.createFromBitmap(rs, inBmp); + Allocation tmpOut = Allocation.createFromBitmap(rs, outBmp); + theIntrinsic.setRadius(5.0f); + theIntrinsic.setInput(tmpIn); + theIntrinsic.forEach(tmpOut); + tmpOut.copyTo(outBmp); + + // Create a bitmap drawable and use a color matrix to de-saturate the image + BitmapDrawable[] layers = new BitmapDrawable[2]; + layers[0] = new BitmapDrawable(getResources(), tmpBmp); + layers[1] = new BitmapDrawable(getResources(), outBmp); + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0); + Paint p = layers[0].getPaint(); + p.setColorFilter(new ColorMatrixColorFilter(cm)); + p = layers[1].getPaint(); + p.setColorFilter(new ColorMatrixColorFilter(cm)); + TransitionDrawable d = new TransitionDrawable(layers); + + // All done + iv.setScaleType(ImageView.ScaleType.CENTER_CROP); + if (!animate) { + iv.setImageDrawable(layers[1]); + } else { + iv.setImageDrawable(d); + } + } + }); + } + + /** + * Disable the ViewPager while a theme change is occuring + */ + public void themeChangeStart() { + lockPager(); + mThemeChanging = true; + ThemeFragment f = getCurrentFragment(); + if (f != null) { + mAppliedBaseTheme = f.getThemePackageName(); + PreferenceUtils.setAppliedBaseTheme(this, mAppliedBaseTheme); + } + } + + /** + * Re-enable the ViewPager and update the "My theme" fragment if available + */ + public void themeChangeEnd(boolean isSuccess) { + mThemeChanging = false; + ThemeFragment f = getCurrentFragment(); + if (f != null) { + // We currently need to recreate the adapter in order to load + // the changes otherwise the adapter returns the original fragments + // TODO: We'll need a better way to handle this to provide a good UX + if (!(f instanceof MyThemeFragment)) { + mAdapter = new ThemesAdapter(); + mPager.setAdapter(mAdapter); + } + if (!isSuccess) { + mAppliedBaseTheme = null; + } + startLoader(LOADER_ID_APPLIED); + } + unlockPager(); + } + + public void lockPager() { + mPager.setScrollingEnabled(false); + } + + public void unlockPager() { + mPager.setScrollingEnabled(true); + } + + public ComponentSelector getComponentSelector() { + return mSelector; + } + + public void showComponentSelector(String component, View v) { + showComponentSelector(component, null, DEFAULT_COMPONENT_ID, v); + } + + public void showComponentSelector(String component, String selectedPkgName, + long selectedCmpntId, View v) { + if (component != null) { + final Resources res = getResources(); + int itemsPerPage = res.getInteger(R.integer.default_items_per_page); + int height = res.getDimensionPixelSize(R.dimen.component_selection_cell_height); + if (MODIFIES_BOOT_ANIM.equals(component)) { + itemsPerPage = res.getInteger(R.integer.bootani_items_per_page); + height = res.getDimensionPixelSize( + R.dimen.component_selection_cell_height_boot_anim); + } else if (MODIFIES_ALARMS.equals(component) || + MODIFIES_NOTIFICATIONS.equals(component) || + MODIFIES_RINGTONES.equals(component)) { + itemsPerPage = 2; + height = res.getDimensionPixelSize( + R.dimen.component_selection_cell_height_sounds); + } + if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { + if (mSaveApplyLayout.getTranslationY() + height != 0) { + mSaveApplyLayout.animate() + .translationY(-height) + .setInterpolator( + new DecelerateInterpolator( + ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) + .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); + } + } + mSelector.show(component, selectedPkgName, selectedCmpntId, itemsPerPage, height); + + // determine if we need to shift the cards up + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + final int top = coordinates[1]; + final int bottom = coordinates[1] + v.getHeight(); + final int statusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); + int selectorTop = getWindowManager().getDefaultDisplay().getHeight() - height; + if (bottom > selectorTop) { + slideContentIntoView(bottom - selectorTop, height); + } else if (top < statusBarHeight) { + slideContentIntoView(top - statusBarHeight, height); + } + } + } + + public void expand() { + if (!mExpanded && !mIsAnimating) { + mExpanded = true; + mContainer.setClickable(false); + mContainer.expand(); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.expand(); + } + setAnimatingStateAndScheduleFinish(); + } + } + + public void collapse(final boolean applyTheme) { + mExpanded = false; + final ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.fadeOutCards(new Runnable() { + public void run() { + mContainer.collapse(); + f.collapse(applyTheme); + } + }); + } + setAnimatingStateAndScheduleFinish(); + } + + public void pickExternalWallpaper() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(TYPE_IMAGE); + startActivityForResult(intent, REQUEST_PICK_WALLPAPER_IMAGE); + mIsPickingImage = true; + } + + public void pickExternalLockscreen() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(TYPE_IMAGE); + startActivityForResult(intent, REQUEST_PICK_LOCKSCREEN_IMAGE); + mIsPickingImage = true; + } + + public void uninstallTheme(String pkgName) { + PackageManager pm = getPackageManager(); + pm.deletePackage(pkgName, new PackageDeleteObserver(), PackageManager.DELETE_ALL_USERS); + } + + private void slideContentIntoView(int yDelta, int selectorHeight) { + ThemeFragment f = getCurrentFragment(); + if (f != null) { + final int offset = getResources().getDimensionPixelSize(R.dimen.content_offset_padding); + if (yDelta > 0) { + yDelta += offset; + } else { + yDelta -= offset; + } + f.slideContentIntoView(yDelta, selectorHeight); + mContainerYOffset = yDelta; + } + } + + private void slideContentBack(final int yDelta) { + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.slideContentBack(yDelta); + } + } + + @Override + protected void onResume() { + super.onResume(); + setCustomBackground(mCustomBackground, mAnimateContentIn); + // clear out any notifications that are being displayed. + NotificationHelper.cancelNotifications(this); + + ThemeManager tm = ThemeManager.getInstance(this); + mThemeChanging = tm.isThemeApplying(); + + if (!mIsPickingImage) { + startLoader(LOADER_ID_APPLIED); + } else { + mIsPickingImage = false; + } + + IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + registerReceiver(mWallpaperChangeReceiver, filter); + } + + @Override + public void onBackPressed() { + final ThemeFragment f = getCurrentFragment(); + if (mSelector.isEnabled()) { + mSelector.hide(); + if (mContainerYOffset != 0) { + slideContentBack(-mContainerYOffset); + mContainerYOffset = 0; + } + if (f != null) f.fadeInCards(); + if (mShowLockScreenWallpaper) { + mShowLockScreenWallpaper = false; + mSelector.resetComponentType(); + } + } else if (mExpanded) { + if (mIsAnimating) { + return; + } + + if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { + hideSaveApplyButton(); + if (f != null) f.clearChanges(); + } + collapse(false); + } else { + if (f != null && f.isShowingConfirmCancelOverlay()) { + f.hideConfirmCancelOverlay(); + } else if (f != null && f.isShowingCustomizeResetLayout()) { + f.hideCustomizeResetLayout(); + } else { + super.onBackPressed(); + } + } + } + + @Override + public void onPause() { + super.onPause(); + unregisterReceiver(mWallpaperChangeReceiver); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + mSelectedTheme = f.getThemePackageName(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onStart() { + super.onStart(); + if (mTypefaceHelperCache.getTypefaceCount() <= 0) { + new TypefacePreloadTask().execute(); + } + mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_WALLPAPER_IMAGE) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.setWallpaperImageUri(uri); + showSaveApplyButton(); + } + } + } else if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_LOCKSCREEN_IMAGE) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.setLockscreenImageUri(uri); + showSaveApplyButton(); + } + } + } else if (requestCode == REQUEST_SYSTEM_WINDOW_PERMISSION) { + if (Settings.canDrawOverlays(this)) { + launchAppThemer(); + } + } + } + + private void animateContentIn() { + Drawable d = mCustomBackground.getDrawable(); + if (d instanceof TransitionDrawable) { + ((TransitionDrawable) d).startTransition((int) ANIMATE_CONTENT_IN_BLUR_DURATION); + } + + if (!mShowLockScreenWallpaper) { + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) + .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION)) + .with(ObjectAnimator.ofFloat(mContainer, "scaleX", 2f, 1f) + .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)) + .with(ObjectAnimator.ofFloat(mContainer, "scaleY", 2f, 1f) + .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)); + set.setStartDelay(mAnimateContentInDelay); + set.start(); + mBottomActionsLayout.setAlpha(0f); + mBottomActionsLayout.animate().alpha(1f).setStartDelay(mAnimateContentInDelay) + .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION); + } else { + mContainer.setAlpha(0f); + mContainer.setVisibility(View.GONE); + } + mAnimateContentIn = false; + } + + private View.OnClickListener mPagerClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + ThemeFragment f = getCurrentFragment(); + if (f != null && !mThemeChanging) { + f.performClick(mPager.isClickedOnContent()); + } + } + }; + + private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mCustomBackground != null) setCustomBackground(mCustomBackground, false); + } + }; + + private ComponentSelector.OnOpenCloseListener mOpenCloseListener = new ComponentSelector.OnOpenCloseListener() { + @Override + public void onSelectorOpened() { + } + + @Override + public void onSelectorClosed() { + } + + @Override + public void onSelectorClosing() { + ThemeFragment f = getCurrentFragment(); + if (f != null && f.componentsChanged() + && mSaveApplyLayout.getVisibility() == View.VISIBLE) { + mSaveApplyLayout.animate() + .translationY(0) + .setInterpolator(new DecelerateInterpolator()) + .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); + } + } + }; + + private ThemeFragment getCurrentFragment() { + // instantiateItem will return the fragment if it already exists and not instantiate it, + // which should be the case for the current fragment. + ThemeFragment f; + try { + f = (mAdapter == null || mPager == null || mAdapter.getCount() <= 0) ? null : + (ThemeFragment) mAdapter.instantiateItem(mPager, mPager.getCurrentItem()); + } catch (Exception e) { + f = null; + Log.e(TAG, "Unable to get current fragment", e); + } + return f; + } + + private void populateCurrentTheme(Cursor c) { + c.moveToPosition(-1); + //Default to first wallpaper + mCurrentWallpaperCmpntId.value = DEFAULT_COMPONENT_ID; + // clear out the previous map + mCurrentTheme.clear(); + while(c.moveToNext()) { + int mixkeyIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_KEY); + int pkgIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_VALUE); + int cmpntIdIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_COMPONENT_ID); + String mixkey = c.getString(mixkeyIdx); + String component = ThemesContract.MixnMatchColumns.mixNMatchKeyToComponent(mixkey); + String pkg = c.getString(pkgIdx); + mCurrentTheme.put(component, pkg); + if (TextUtils.equals(component, ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { + mCurrentTheme.remove(ThemesColumns.MODIFIES_LOCKSCREEN); + } + if (TextUtils.equals(component, ThemesColumns.MODIFIES_LOCKSCREEN)) { + mCurrentTheme.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); + } + if (cmpntIdIdx >= 0 && TextUtils.equals(component, ThemesColumns.MODIFIES_LAUNCHER)) { + mCurrentWallpaperCmpntId.value = c.getLong(cmpntIdIdx); + } + } + } + + private View.OnClickListener mOnShopThemesClicked = new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.setClassName(THEME_STORE_PACKAGE, THEME_STORE_ACTIVITY); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + lauchGetThemes(); + } + } + }; + + private void startLoader(int loaderId) { + final LoaderManager manager = getSupportLoaderManager(); + final Loader loader = manager.getLoader(loaderId); + if (loader != null) { + manager.restartLoader(loaderId, null, this); + } else { + manager.initLoader(loaderId, null, this); + } + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + if (mThemeChanging) return; + + if (mExpanded && !mActivityResuming) { + mRestartLoaderOnCollapse = true; + return; + } + + switch (loader.getId()) { + case LOADER_ID_INSTALLED_THEMES: + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + int selectedThemeIndex = -1; + if (TextUtils.isEmpty(mSelectedTheme)) mSelectedTheme = mAppliedBaseTheme; + while(data.moveToNext()) { + if (mSelectedTheme.equals(data.getString( + data.getColumnIndex(ThemesColumns.PKG_NAME)))) { + // we need to add one here since the first card is "My theme" + selectedThemeIndex = data.getPosition(); + mSelectedTheme = null; + break; + } + } + data.moveToFirst(); + mAdapter.swapCursor(data); + mAdapter.notifyDataSetChanged(); + if (selectedThemeIndex >= 0) { + mPager.setCurrentItem(selectedThemeIndex, false); + + if (mThemeToApply != null) { + ThemeFragment f = getCurrentFragment(); + f.applyThemeWhenPopulated(mThemeToApply, mComponentsToApply); + mThemeToApply = null; + } + } + if (mAnimateContentIn) animateContentIn(); + mActivityResuming = true; + break; + case LOADER_ID_APPLIED: + startLoader(LOADER_ID_INSTALLED_THEMES); + populateCurrentTheme(data); + break; + } + } + + @Override + public void onLoaderReset(Loader loader) { + switch (loader.getId()) { + case LOADER_ID_INSTALLED_THEMES: + mAdapter.swapCursor(null); + mAdapter.notifyDataSetChanged(); + break; + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_INSTALLED_THEMES: + mAppliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(this); + break; + case LOADER_ID_APPLIED: + //TODO: Mix n match query should only be done once + break; + } + return CursorLoaderHelper.chooserActivityCursorLoader(this, id, mAppliedBaseTheme); + } + + public Map getSelectedComponentsMap() { + return getCurrentFragment().getSelectedComponentsMap(); + } + + public class ThemesAdapter extends NewFragmentStatePagerAdapter { + private ArrayList mInstalledThemes; + private String mAppliedThemeTitle; + private String mAppliedThemeAuthor; + private HashMap mRepositionedFragments; + + public ThemesAdapter() { + super(getSupportFragmentManager()); + mRepositionedFragments = new HashMap(); + } + + @Override + public Fragment getItem(int position) { + ThemeFragment f = null; + MutableLong wallpaperCmpntId; + if (mInstalledThemes != null) { + final String pkgName = mInstalledThemes.get(position); + if (pkgName.equals(mAppliedBaseTheme)) { + f = MyThemeFragment.newInstance(mAppliedBaseTheme, mAppliedThemeTitle, + mAppliedThemeAuthor, mAnimateContentIn, mShowLockScreenWallpaper); + wallpaperCmpntId = mCurrentWallpaperCmpntId; + } else { + f = ThemeFragment.newInstance(pkgName, mAnimateContentIn); + wallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); + } + f.setCurrentTheme(mCurrentTheme, wallpaperCmpntId); + } + return f; + } + + @Override + public long getItemId(int position) { + if (mInstalledThemes != null) { + final String pkgName = mInstalledThemes.get(position); + return pkgName.hashCode(); + } + return 0; + } + + @Override + public int getItemPosition(Object object) { + final ThemeFragment f = (ThemeFragment) object; + final String pkgName = f != null ? f.getThemePackageName() : null; + if (pkgName != null && mRepositionedFragments.containsKey(pkgName)) { + final int position = mRepositionedFragments.get(pkgName); + mRepositionedFragments.remove(pkgName); + return position; + } + return super.getItemPosition(object); + } + + /** + * The first card should be the user's currently applied theme components so we + * will always return at least 1 or mCursor.getCount() + 1 + * @return + */ + public int getCount() { + return mInstalledThemes == null ? 0 : mInstalledThemes.size(); + } + + public void swapCursor(Cursor c) { + if (c != null && c.getCount() != 0) { + ArrayList previousOrder = mInstalledThemes == null ? null + : new ArrayList(mInstalledThemes); + mInstalledThemes = new ArrayList(c.getCount()); + mRepositionedFragments.clear(); + c.moveToPosition(-1); + while (c.moveToNext()) { + final int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + final String pkgName = c.getString(pkgIdx); + if (pkgName.equals(mAppliedBaseTheme)) { + final int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); + final int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); + mAppliedThemeTitle = c.getString(titleIdx); + mAppliedThemeAuthor = c.getString(authorIdx); + } + mInstalledThemes.add(pkgName); + + // track any themes that may have changed position + if (previousOrder != null && previousOrder.contains(pkgName)) { + int index = previousOrder.indexOf(pkgName); + if (index != c.getPosition()) { + mRepositionedFragments.put(pkgName, c.getPosition()); + } + } else { + mRepositionedFragments.put(pkgName, c.getPosition()); + } + } + // check if any themes are no longer in the new list + if (previousOrder != null) { + for (String pkgName : previousOrder) { + if (!mInstalledThemes.contains(pkgName)) { + mRepositionedFragments.put(pkgName, POSITION_NONE); + } + } + } + } else { + mInstalledThemes = null; + } + } + + public void removeTheme(String pkgName) { + if (mInstalledThemes == null) return; + + if (mInstalledThemes.contains(pkgName)) { + final int count = mInstalledThemes.size(); + final int index = mInstalledThemes.indexOf(pkgName); + // reposition all the fragments after the one being removed + for (int i = index + 1; i < count; i++) { + mRepositionedFragments.put(mInstalledThemes.get(i), i - 1); + } + // Now remove this theme and add it to mRepositionedFragments with POSITION_NONE + mInstalledThemes.remove(pkgName); + mRepositionedFragments.put(pkgName, POSITION_NONE); + // now we can call notifyDataSetChanged() + notifyDataSetChanged(); + } + } + } + + private class TypefacePreloadTask extends AsyncTask { + + @Override + protected Object doInBackground(Object[] params) { + String[] projection = { ThemesColumns.PKG_NAME }; + String selection = ThemesColumns.MODIFIES_FONTS + "=?"; + String[] selectionArgs = { "1" }; + Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, projection, selection, + selectionArgs, null); + if (c != null) { + while (c.moveToNext()) { + mTypefaceHelperCache.getHelperForTheme(ChooserActivity.this, c.getString(0)); + } + c.close(); + } + return null; + } + } + + /** + * Internal delete callback from the system + */ + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { + public void packageDeleted(final String packageName, int returnCode) throws RemoteException { + if (returnCode == PackageManager.DELETE_SUCCEEDED) { + Log.d(TAG, "Delete succeeded"); + mHandler.post(new Runnable() { + @Override + public void run() { + mAdapter.removeTheme(packageName); + } + }); + } else { + Log.e(TAG, "Delete failed with returnCode " + returnCode); + } + } + } + + public void expandContentAndAnimateLockScreenCardIn() { + mHandler.post(new Runnable() { + @Override + public void run() { + expand(); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) + .setDuration(ANIMATE_CARDS_IN_DURATION)); + set.setStartDelay(mAnimateContentInDelay); + set.start(); + mContainer.setVisibility(View.VISIBLE); + getCurrentFragment().showLockScreenCard(); + } + }, ANIMATE_CARDS_IN_DURATION); + } + }); + } + + private void launchAppThemer() { + PreferenceUtils.setShowPerAppThemeNewTag(ChooserActivity.this, false); + Intent intent = new Intent(ChooserActivity.this, PerAppThemingWindow.class); + startService(intent); + finish(); + } + + private void requestSystemWindowPermission() { + Intent intent = new Intent (Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, REQUEST_SYSTEM_WINDOW_PERMISSION); + } +} diff --git a/src/org/cyanogenmod/theme/chooser/ComponentCardView.java b/src/org/cyanogenmod/theme/chooser/ComponentCardView.java new file mode 100644 index 0000000..54d97c9 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ComponentCardView.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.IntEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOverlay; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class ComponentCardView extends LinearLayout { + public static final int CARD_FADE_DURATION = 300; + + private static final float SEMI_OPAQUE_ALPHA = 0.4f; + + protected TextView mLabel; + protected View mEmptyView; + protected View mContentView; + + // Expanded Padding + int mExpandPadLeft; + int mExpandPadTop; + int mExpandPadRight; + int mExpandPadBottom; + + // The background drawable is returning an alpha of 0 regardless of what we set it to + // so this will help us keep track of what the drawable's alpha is at. + private int mBackgroundAlpha = 255; + + public ComponentCardView(Context context) { + this(context, null); + } + + public ComponentCardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ComponentCardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setEnabled(false); + } + + @Override + protected void onFinishInflate() { + mLabel = (TextView) findViewById(R.id.label); + + Resources r = getContext().getResources(); + mExpandPadLeft = + (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingLeft(); + mExpandPadTop = + (int) r.getDimension(R.dimen.card_padding_top) + getPaddingTop(); + mExpandPadRight = + (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingRight(); + mExpandPadBottom = + (int) r.getDimension(R.dimen.card_padding_bottom) + getPaddingBottom(); + mEmptyView = findViewById(R.id.empty); + mContentView = findViewById(R.id.content); + } + + public void expand(boolean showLabel) { + setEnabled(true); + TransitionDrawable bg = null; + if (getBackground() instanceof TransitionDrawable) { + bg = (TransitionDrawable) getBackground(); + } + if (bg != null) { + Rect paddingRect = new Rect(); + bg.getPadding(paddingRect); + } + + setPadding(mExpandPadLeft, 0, mExpandPadRight, mExpandPadBottom); + + if (mLabel != null) { + mLabel.setAlpha(showLabel ? 1f : 0f); + mLabel.setVisibility(View.VISIBLE); + } + } + + public void animateExpand() { + if (getBackground() instanceof TransitionDrawable) { + TransitionDrawable background = (TransitionDrawable) getBackground(); + if (mLabel != null) { + mLabel.setVisibility(View.VISIBLE); + mLabel.setAlpha(0f); + mLabel.animate().alpha(1f).setDuration(CARD_FADE_DURATION).start(); + } + background.startTransition(CARD_FADE_DURATION); + } + } + + public void collapse() { + setEnabled(false); + if (mLabel != null) { + mLabel.setVisibility(View.GONE); + } + setPadding(0, 0, 0, 0); + } + + public void animateFadeOut() { + if (mLabel != null) { + mLabel.animate().alpha(0f).setDuration(CARD_FADE_DURATION); + } + + if (getBackground() instanceof TransitionDrawable) { + TransitionDrawable background = (TransitionDrawable) getBackground(); + background.reverseTransition(CARD_FADE_DURATION); + } + } + + /** + * Animates the card background and the title to 20% opacity. + */ + public void animateCardFadeOut() { + this.animate().alpha(SEMI_OPAQUE_ALPHA).setDuration(CARD_FADE_DURATION); + } + + /** + * Animates the card background and the title back to full opacity. + */ + public void animateCardFadeIn() { + this.animate().alpha(1f).setDuration(CARD_FADE_DURATION); + } + + /** + * Animates a change in the content of the card + * @param v View in card to animate + * @param overlay Drawable to animate as a ViewOverlay + * @param duration Duration of animation + */ + public void animateContentChange(View v, final Drawable overlay, long duration) { + final ViewOverlay viewOverlay = this.getOverlay(); + viewOverlay.add(overlay); + final int x = getRelativeLeft(v); + final int y = getRelativeTop(v); + final int width = v.getWidth(); + final int height = v.getHeight(); + overlay.setBounds(x, y, x + v.getWidth(), y + v.getHeight()); + + final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); + overlayAnimator.setDuration(duration); + overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + overlay.setAlpha((int) (255 * value)); + int newWidth = (int) (value * width); + int newHeight = (int) (value * height); + int dw = (width - newWidth) / 2; + int dh = (height - newHeight) / 2; + overlay.setBounds(x + dw, y + dh, x + dw + newWidth, y + dh + newHeight); + } + }); + overlayAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + // Clear out the ViewOverlay now that we are done animating + viewOverlay.clear(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(v, "alpha", 0f, 1f)) + .with(ObjectAnimator.ofFloat(v, "scaleX", 0f, 1f)) + .with(ObjectAnimator.ofFloat(v, "scaleY", 0f, 1f)); + set.setDuration(duration); + + set.start(); + overlayAnimator.start(); + } + + public void setEmptyViewEnabled(boolean enabled) { + if (mEmptyView != null) { + mEmptyView.setVisibility(enabled ? View.VISIBLE : View.GONE); + } + if (mContentView != null) { + mContentView.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); + } + } + + public boolean isShowingEmptyView() { + return mEmptyView != null && mEmptyView.getVisibility() == View.VISIBLE; + } + + private int getRelativeTop(View v) { + if (v.getParent() == this) { + return v.getTop(); + } else { + return v.getTop() + ((View) v.getParent()).getTop(); + } + } + + private int getRelativeLeft(View v) { + if (v.getParent() == this) { + return v.getLeft(); + } else { + return v.getLeft() + ((View) v.getParent()).getLeft(); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/ComponentSelector.java b/src/org/cyanogenmod/theme/chooser/ComponentSelector.java new file mode 100644 index 0000000..ad0b68c --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ComponentSelector.java @@ -0,0 +1,934 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; +import android.widget.ComponentSelectorLinearLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.cyanogenmod.theme.util.AudioUtils; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.ThemedTypefaceHelper; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; + +import java.util.Map; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; + +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_FONT; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; + +public class ComponentSelector extends LinearLayout + implements LoaderManager.LoaderCallbacks { + private static final String TAG = ComponentSelector.class.getSimpleName(); + + public static final boolean DEBUG_SELECTOR = false; + + public static final String EXTERNAL_WALLPAPER = "external"; + + public static final String MOD_LOCK = "mod_lock"; + + private static final int EXTRA_WALLPAPER_COMPONENTS = 2; + + private static final int EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS = 3; + + protected static final long DEFAULT_COMPONENT_ID = 0; + + private Context mContext; + private LayoutInflater mInflater; + private ComponentSelectorLinearLayout mContent; + private LinearLayout.LayoutParams mItemParams; + private LinearLayout.LayoutParams mSoundItemParams = + new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1.0f); + + private String mComponentType; + private int mBatteryStyle; + private int mItemsPerPage; + private String mAppliedComponentPkgName; + private String mSelectedComponentPkgName; + private long mSelectedComponentId; + + // animations for bringing selector in and out of view + private Animation mAnimateIn; + private Animation mAnimateOut; + + private OnItemClickedListener mListener; + + private OnOpenCloseListener mOpenCloseListener; + + private MediaPlayer mMediaPlayer; + private ImageView mCurrentPlayPause; + + private TypefaceHelperCache mTypefaceCache; + + private ThemesObserver mThemesObserver; + + public static final String IS_LIVE_LOCK_SCREEN_VIEW = "is_live_lock_screen_view"; + private View mPrevLockScreenView; + + public ComponentSelector(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ComponentSelector); + mItemsPerPage = a.getInt(R.styleable.ComponentSelector_itemsPerPage, + context.getResources().getInteger(R.integer.default_items_per_page)); + a.recycle(); + + mContext = context; + mInflater = LayoutInflater.from(context); + // TODO: Load from settings once available + mBatteryStyle = 0; /*Settings.System.getInt(context.getContentResolver(), + Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ + + mAnimateIn = AnimationUtils.loadAnimation(mContext, + R.anim.component_selection_animate_in); + mAnimateOut = AnimationUtils.loadAnimation(mContext, + R.anim.component_selection_animate_out); + mAnimateIn.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + if (mOpenCloseListener != null) mOpenCloseListener.onSelectorOpened(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + mAnimateOut.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (mOpenCloseListener != null) mOpenCloseListener.onSelectorClosing(); + } + + @Override + public void onAnimationEnd(Animation animation) { + setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + if (mCurrentPlayPause != null) { + mCurrentPlayPause.setImageResource(R.drawable.media_sound_selector_preview); + mCurrentPlayPause = null; + } + } + }); + mTypefaceCache = TypefaceHelperCache.getInstance(); + mThemesObserver = new ThemesObserver(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContent = (ComponentSelectorLinearLayout) findViewById(R.id.content); + setEnabled(false); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mThemesObserver.register(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mThemesObserver.unregister(); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + // Window visibility transitions as follows + // VISIBLE -> INVISIBLE -> GONE + // GONE -> INVISIBLE -> VISIBLE + if (visibility == View.GONE) { + mThemesObserver.unregister(); + } else if (visibility == View.VISIBLE) { + mThemesObserver.register(); + } + } + + public void resetComponentType() { + mComponentType = null; + } + + public void setComponentType(String component) { + setComponentType(component, null, DEFAULT_COMPONENT_ID); + } + + public void setComponentType(String component, String selectedPkgName) { + setComponentType(component, null, DEFAULT_COMPONENT_ID); + } + + public void setComponentType(String component, String selectedPkgName, + long selectedComponentId) { + mSelectedComponentPkgName = selectedPkgName; + mSelectedComponentId = selectedComponentId; + if (mComponentType == null || !mComponentType.equals(component)) { + mContent.removeAllViews(); + mComponentType = component; + ((FragmentActivity) mContext).getSupportLoaderManager().restartLoader( + getLoaderIdFromComponent(component), null, this); + } else if (mComponentType != null) { + int count = mContent.getChildCount(); + final Resources res = getResources(); + boolean highlightTitle; + Map selectedComponents = null; + if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { + selectedComponents = ((ChooserActivity)mContext) + .getSelectedComponentsMap(); + } + for (int i = 0; i < count; i++) { + final View child = mContent.getChildAt(i); + final TextView tv = (TextView) child.findViewById(R.id.title); + // The child itself may have the tag, or in the case of sounds its the grandchild, + // either way the parent of the textview will have the pkgName in its tag + final View viewWithTag = (View) tv.getParent(); + final String pkgName = (String) viewWithTag.getTag(R.id.tag_key_package_name); + Long cmpntId = (Long) viewWithTag.getTag(R.id.tag_key_component_id); + if (cmpntId == null) { + cmpntId = DEFAULT_COMPONENT_ID; + } + Boolean isLLS = (Boolean) viewWithTag.getTag(R.id.tag_key_live_lock_screen); + highlightTitle = false; + if (selectedComponents != null && isLLS != null) { + if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) + && !isLLS) || (TextUtils.equals(selectedComponents.get( + MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS)) { + highlightTitle = true; + } + } else if (pkgName.equals(selectedPkgName) && cmpntId == selectedComponentId) { + highlightTitle = true; + } + if (highlightTitle) { + tv.setTextColor(res.getColor(R.color.component_selection_current_text_color)); + } else { + tv.setTextColor(res.getColor(android.R.color.white)); + } + } + } + } + + public String getComponentType() { + return mComponentType; + } + + public void setNumItemsPerPage(int itemsPerPage) { + if (mItemsPerPage != itemsPerPage) { + mItemsPerPage = itemsPerPage; + } + } + + public void setHeight(int height) { + ViewGroup.LayoutParams params = mContent.getLayoutParams(); + if (params.height != height) { + params.height = height; + mContent.setLayoutParams(params); + requestLayout(); + } + } + + public void show(String componentType, int itemsPerPage, int height) { + show(componentType, null, itemsPerPage, height); + } + + public void show(String componentType, String selectedPkgName, int itemsPerPage, int height) { + show(componentType, selectedPkgName, DEFAULT_COMPONENT_ID, itemsPerPage, height); + } + + public void show(String componentType, String selectedPkgName, long selectedComponentId, + int itemsPerPage, int height) { + setNumItemsPerPage(itemsPerPage); + setHeight(height); + setComponentType(componentType, selectedPkgName, selectedComponentId); + show(); + } + + public void show() { + if (getVisibility() == View.GONE) { + setEnabled(true); + setVisibility(View.VISIBLE); + startAnimation(mAnimateIn); + } + } + + public void hide() { + if (getVisibility() == View.VISIBLE && isEnabled()) { + setEnabled(false); + startAnimation(mAnimateOut); + } + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) mMediaPlayer.stop(); + } + + private int getLoaderIdFromComponent(String component) { + if (MODIFIES_STATUS_BAR.equals(component)) { + return LOADER_ID_STATUS_BAR; + } + if (MODIFIES_NAVIGATION_BAR.equals(component)) { + return LOADER_ID_NAVIGATION_BAR; + } + if (MODIFIES_FONTS.equals(component)) { + return LOADER_ID_FONT; + } + if (MODIFIES_ICONS.equals(component)) { + return LOADER_ID_ICONS; + } + if (MODIFIES_OVERLAYS.equals(component)) { + return LOADER_ID_STYLE; + } + if (MODIFIES_LAUNCHER.equals(component)) { + return LOADER_ID_WALLPAPER; + } + if (MODIFIES_BOOT_ANIM.equals(component)) { + return LOADER_ID_BOOT_ANIMATION; + } + if (MODIFIES_RINGTONES.equals(component)) { + return LOADER_ID_RINGTONE; + } + if (MODIFIES_NOTIFICATIONS.equals(component)) { + return LOADER_ID_NOTIFICATION; + } + if (MODIFIES_ALARMS.equals(component)) { + return LOADER_ID_ALARM; + } + if (MODIFIES_LOCKSCREEN.equals(component)) { + return LOADER_ID_LOCKSCREEN; + } + return -1; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return CursorLoaderHelper.componentSelectorCursorLoader(mContext, id); + } + + @Override + public void onLoadFinished(Loader loader, final Cursor data) { + int currentLoaderId = loader.getId(); + int count = data.getCount(); + int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; + final Resources res = getResources(); + int dividerPadding = res.getDimensionPixelSize(R.dimen.component_divider_padding_top); + int dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height); + MatrixCursor lockScreenMatrixCursor = null; + + switch (currentLoaderId) { + case LOADER_ID_ALARM: + case LOADER_ID_NOTIFICATION: + case LOADER_ID_RINGTONE: + mItemParams = new LayoutParams(screenWidth, + ViewGroup.LayoutParams.MATCH_PARENT); + // Sounds are a special case where there are two items laid out + // vertically. This layout is added as a single item so we need to + // adjust the count by dividing by the number of items per page and + // rounding up so we include all items. + count = (int) Math.ceil((double)count / mItemsPerPage); + mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); + break; + case LOADER_ID_BOOT_ANIMATION: + dividerPadding = res.getDimensionPixelSize( + R.dimen.component_divider_padding_top_bootani); + dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height_bootani); + // fall through to default + default: + mItemParams = new LayoutParams(screenWidth / mItemsPerPage, + ViewGroup.LayoutParams.MATCH_PARENT); + if (currentLoaderId == LOADER_ID_WALLPAPER || + currentLoaderId == LOADER_ID_LOCKSCREEN) { + count += EXTRA_WALLPAPER_COMPONENTS; + } + + if (currentLoaderId == LOADER_ID_LOCKSCREEN) { + lockScreenMatrixCursor = splitLockScreenCursor(data); + if (lockScreenMatrixCursor != null) { + count = lockScreenMatrixCursor.getCount() + EXTRA_WALLPAPER_COMPONENTS; + } + } + + mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + break; + } + + mContent.setDividerPadding(dividerPadding); + mContent.setDividerHeight(dividerHeight); + + new LoadItemsTask().execute((lockScreenMatrixCursor != null) + ? lockScreenMatrixCursor : data, count); + } + + /* Some themes might contain a lock wallpaper AND a live lock screen. + * ThemesProvider will return one single row containing both thumbnail paths + * (ThemesProvider groups by theme_id) so we need to create a cursor on the + * fly to split that row into 2 to properly generate a view for each thumbnail + */ + private MatrixCursor splitLockScreenCursor(Cursor data) { + int lockWallPaperThumbnailIndx, llsThumbnailIndx, pkgIndx; + String lockWallPaperThumbnail, liveLockScreenThumbnail, pkgName; + MatrixCursor lockScreenMatrixCursor; + int needToSplitRowAt = -1; + + lockWallPaperThumbnailIndx = data.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); + llsThumbnailIndx = data.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL); + pkgIndx = data.getColumnIndex(ThemesColumns.PKG_NAME); + + if (lockWallPaperThumbnailIndx < 0 || llsThumbnailIndx < 0 || pkgIndx < 0) { + //An invalid cursor was provided, we can't continue processing + Log.e(TAG, "Failed to process cursor due to missing columns"); + return null; + } + + //Let's find out if we really need to allocate a MatrixCursor. + //If we find at least one row with valid data in lock_wallpaper_thumbnail AND + //live_lock_screen_thumbnail it means we do. + data.moveToPosition(-1); + while (data.moveToNext()) { + lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); + liveLockScreenThumbnail = data.getString(llsThumbnailIndx); + if (!TextUtils.isEmpty(lockWallPaperThumbnail) + && !TextUtils.isEmpty(liveLockScreenThumbnail)) { + needToSplitRowAt = data.getPosition(); + break; + } + } + + if (needToSplitRowAt == -1) return null; + + lockScreenMatrixCursor = new MatrixCursor(data.getColumnNames()); + //Clone all the *regular* rows up to needToSplitRowAt + for (int indx = 0; indx < needToSplitRowAt; indx++) { + data.moveToPosition(indx); + lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); + } + if (needToSplitRowAt == 0) { + data.moveToPosition(-1); + } + while (data.moveToNext()) { + lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); + liveLockScreenThumbnail = data.getString(llsThumbnailIndx); + if (!TextUtils.isEmpty(lockWallPaperThumbnail) + && !TextUtils.isEmpty(liveLockScreenThumbnail)) { + pkgName = data.getString(pkgIndx); + + MatrixCursor.RowBuilder lockWallpaperRow = lockScreenMatrixCursor.newRow(); + MatrixCursor.RowBuilder liveLockScreenRow = lockScreenMatrixCursor.newRow(); + + for (String col : data.getColumnNames()) { + if (TextUtils.equals(col, PreviewColumns.LOCK_WALLPAPER_THUMBNAIL)) { + lockWallpaperRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + lockWallPaperThumbnail); + liveLockScreenRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, null); + } else if (TextUtils.equals(col, PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL)) { + lockWallpaperRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, null); + liveLockScreenRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + liveLockScreenThumbnail); + } else if (TextUtils.equals(col, MODIFIES_LIVE_LOCK_SCREEN)) { + lockWallpaperRow.add(MODIFIES_LIVE_LOCK_SCREEN, 0); + liveLockScreenRow.add(MODIFIES_LIVE_LOCK_SCREEN, 1); + } else { + int colIndx = data.getColumnIndex(col); + lockWallpaperRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, + colIndx)); + liveLockScreenRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, + colIndx)); + } + } + } else { + //This is a regular row, so just clone it + lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); + } + } + return lockScreenMatrixCursor; + } + + @Override + public void onLoaderReset(Loader loader) { + } + + public void setOnItemClickedListener(OnItemClickedListener listener) { + mListener = listener; + } + + public void setOnOpenCloseListener(OnOpenCloseListener listener) { + mOpenCloseListener = listener; + } + + private View newView(Cursor cursor, int position, ViewGroup container) { + if (MODIFIES_STATUS_BAR.equals(mComponentType)) { + return newStatusBarView(cursor, container, position); + } + if (MODIFIES_NAVIGATION_BAR.equals(mComponentType)) { + return newNavBarView(cursor, container, position); + } + if (MODIFIES_FONTS.equals(mComponentType)) { + return newFontView(cursor, container, position); + } + if (MODIFIES_ICONS.equals(mComponentType)) { + return newIconView(cursor, container, position); + } + if (MODIFIES_OVERLAYS.equals(mComponentType)) { + return newStyleView(cursor, container, position); + } + if (MODIFIES_LAUNCHER.equals(mComponentType)) { + return newWallpapersView(cursor, container, position, + cursor.getColumnIndex(PreviewColumns.WALLPAPER_THUMBNAIL), false, + EXTRA_WALLPAPER_COMPONENTS); + } + if (MODIFIES_BOOT_ANIM.equals(mComponentType)) { + return newBootanimationView(cursor, container, position); + } + if (MODIFIES_RINGTONES.equals(mComponentType) || + MODIFIES_NOTIFICATIONS.equals(mComponentType) || + MODIFIES_ALARMS.equals(mComponentType)) { + return newSoundView(cursor, container, position, mComponentType); + } + if (MODIFIES_LOCKSCREEN.equals(mComponentType)) { + boolean isLiveLockScreen = false; + if (position >= EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS) { + cursor.moveToPosition(position - EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); + int liveLockIndex = cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); + isLiveLockScreen = liveLockIndex >= 0 && + cursor.getInt(liveLockIndex) == 1; + } + int index = isLiveLockScreen + ? cursor.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL) + : cursor.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); + return newWallpapersView(cursor, container, position, index, isLiveLockScreen, + EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); + } + return null; + } + + private View newStatusBarView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.status_bar_component_selection_item, + parent, false); + int wifiIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); + int signalIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); + int bluetoothIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); + int batteryIndex = cursor.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); + int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.slot1)).setImageBitmap( + Utils.loadBitmapBlob(cursor, wifiIndex)); + ((ImageView) v.findViewById(R.id.slot2)).setImageBitmap( + Utils.loadBitmapBlob(cursor, signalIndex)); + ((ImageView) v.findViewById(R.id.slot3)).setImageBitmap( + Utils.loadBitmapBlob(cursor, bluetoothIndex)); + ((ImageView) v.findViewById(R.id.slot4)).setImageBitmap( + Utils.loadBitmapBlob(cursor, batteryIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.findViewById(R.id.container).setBackground( + new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newNavBarView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.navigation_bar_component_selection_item, parent, + false); + int backIndex = cursor.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); + int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.back)).setImageBitmap( + Utils.loadBitmapBlob(cursor, backIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.findViewById(R.id.container).setBackground( + new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newFontView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.font_component_selection_item, parent, false); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + TextView preview = (TextView) v.findViewById(R.id.text_preview); + String pkgName = cursor.getString(pkgNameIndex); + + ThemedTypefaceHelper helper = mTypefaceCache.getHelperForTheme(mContext, pkgName); + Typeface typefaceNormal = helper.getTypeface(Typeface.NORMAL); + preview.setTypeface(typefaceNormal); + + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newIconView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, + false); + int iconIndex = cursor.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( + Utils.loadBitmapBlob(cursor, iconIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newStyleView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, + false); + int styleIndex = cursor.getColumnIndex(PreviewColumns.STYLE_THUMBNAIL); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( + Utils.loadBitmapBlob(cursor, styleIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newWallpapersView(Cursor cursor, ViewGroup parent, int position, + int wallpaperIndex, boolean isLiveLockScreen, int numExtraComponents) { + View v = mInflater.inflate(R.layout.wallpaper_component_selection_item, parent, + false); + ImageView iv = (ImageView) v.findViewById(R.id.icon); + if (position == 0) { + iv.setImageResource(R.drawable.img_wallpaper_none); + v.setTag(R.id.tag_key_package_name, ""); + ((TextView) v.findViewById(R.id.title)).setText(R.string.wallpaper_none_title); + } else if (position == 1) { + iv.setImageResource(R.drawable.img_wallpaper_external); + v.setTag(R.id.tag_key_package_name, EXTERNAL_WALLPAPER); + ((TextView) v.findViewById(R.id.title)) + .setText(R.string.wallpaper_external_title); + } else if (numExtraComponents == EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS && position == 2) { + // TODO: update drawable once the asset is provided by design + iv.setImageResource(android.R.drawable.ic_lock_lock); + v.setTag(R.id.tag_key_package_name, MOD_LOCK); + ((TextView) v.findViewById(R.id.title)) + .setText(R.string.mod_lock_title); + } else { + cursor.moveToPosition(position - numExtraComponents); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); + long cmpntId = (cmpntIdIndex >= 0) ? + cursor.getLong(cmpntIdIndex) : DEFAULT_COMPONENT_ID; + iv.setImageBitmap( + Utils.loadBitmapBlob(cursor, wallpaperIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setTag(R.id.tag_key_component_id, cmpntId); + v.setTag(R.id.tag_key_live_lock_screen, isLiveLockScreen); + v.findViewById(R.id.live_lock_screen_badge) + .setVisibility(isLiveLockScreen ? View.VISIBLE : View.GONE); + } + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newBootanimationView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.bootani_component_selection_item, parent, + false); + int wallpaperIndex = cursor.getColumnIndex(PreviewColumns.BOOTANIMATION_THUMBNAIL); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.preview)).setImageBitmap( + Utils.loadBitmapBlob(cursor, wallpaperIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newSoundView(Cursor cursor, ViewGroup parent, int position, + final String component) { + LinearLayout container = (LinearLayout) mInflater.inflate( + R.layout.component_selection_sounds_pager_item, parent, false); + container.setWeightSum(mItemsPerPage); + for (int i = 0; i < mItemsPerPage; i++) { + int index = position * mItemsPerPage + i; + if (cursor.getCount() <= index) continue; + cursor.moveToPosition(index); + View v = mInflater.inflate(R.layout.sound_component_selection_item, parent, + false); + final int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + container.addView(v, mSoundItemParams); + final View playButton = v.findViewById(R.id.play_button); + playButton.setTag(cursor.getString(pkgNameIndex)); + playButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + int type; + String pkgName = (String) v.getTag(); + if (component.equals(MODIFIES_RINGTONES)) { + type = RingtoneManager.TYPE_RINGTONE; + } else if (component.equals(MODIFIES_NOTIFICATIONS)) { + type = RingtoneManager.TYPE_NOTIFICATION; + } else { + type = RingtoneManager.TYPE_ALARM; + } + boolean shouldStop = playButton == mCurrentPlayPause; + try { + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); + if (mCurrentPlayPause != null) { + mCurrentPlayPause.setImageResource( + R.drawable.media_sound_selector_preview); + } + mCurrentPlayPause = null; + } + if (mCurrentPlayPause != playButton && !shouldStop) { + AudioUtils.loadThemeAudible(mContext, type, pkgName, + mMediaPlayer); + mMediaPlayer.start(); + mCurrentPlayPause = (ImageView) playButton; + mCurrentPlayPause.setImageResource( + R.drawable.media_sound_selector_stop); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to play preview sound", e); + } + } + }); + } + + return container; + } + + private class LoadItemsTask extends AsyncTask { + + @Override + protected Void doInBackground(Object... params) { + Cursor c = (Cursor) params[0]; + int count = (Integer) params[1]; + for (int i = 0; i < count && !isCancelled(); i++) { + final View v = newView(c, i, mContent); + mContent.post(new Runnable() { + @Override + public void run() { + mContent.addView(v, mItemParams); + } + }); + } + + if (c instanceof MatrixCursor) { + c.close(); + } + + // destroy the loader now that we are done with it + ComponentSelector.this.post(new Runnable() { + @Override + public void run() { + ((FragmentActivity)mContext).getSupportLoaderManager().destroyLoader( + getLoaderIdFromComponent(mComponentType)); + } + }); + return null; + } + } + + private void setTitle(TextView titleView, Cursor cursor) { + String pkgName = cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)); + int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); + long cmpntId = DEFAULT_COMPONENT_ID; + if (cmpntIdIndex >= 0) { + cmpntId = cursor.getLong(cmpntIdIndex); + } + if (Utils.getDefaultThemePackageName(mContext).equals(pkgName)) { + titleView.setText(mContext.getString(R.string.default_tag_text)); + titleView.setTypeface(titleView.getTypeface(), Typeface.BOLD); + } else { + titleView.setText(cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); + } + boolean highlightTitle = false; + if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { + Map selectedComponents = ((ChooserActivity)mContext) + .getSelectedComponentsMap(); + int isLLS = cursor.getInt(cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN)); + if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) + && isLLS == 0) || (TextUtils.equals( + selectedComponents.get(MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS == 1)) { + highlightTitle = true; + } + } else if (pkgName.equals(mSelectedComponentPkgName) && cmpntId == mSelectedComponentId) { + highlightTitle = true; + } + if (highlightTitle) { + titleView.setTextColor(getResources().getColor( + R.color.component_selection_current_text_color)); + } + } + + private OnClickListener mItemClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + long cmpntId = DEFAULT_COMPONENT_ID; + String pkgName = (String) v.getTag(R.id.tag_key_package_name); + Long cmpntIdTag = (Long) v.getTag(R.id.tag_key_component_id); + if (cmpntIdTag != null) { + cmpntId = cmpntIdTag; + } + Boolean isLiveLock = (Boolean) v.getTag(R.id.tag_key_live_lock_screen); + boolean isSamePkgButDifferentLockScreen = false; + Bundle params = null; + if (isLiveLock != null) { + params = new Bundle(); + params.putBoolean(IS_LIVE_LOCK_SCREEN_VIEW, isLiveLock); + + if (pkgName.equals(mSelectedComponentPkgName) && v != mPrevLockScreenView) { + isSamePkgButDifferentLockScreen = true; + } + mPrevLockScreenView = v; + } + if (DEBUG_SELECTOR) Toast.makeText(mContext, pkgName, Toast.LENGTH_SHORT).show(); + if (mListener != null && (isSamePkgButDifferentLockScreen || + !pkgName.equals(mSelectedComponentPkgName) || + pkgName.equals(EXTERNAL_WALLPAPER) || cmpntId != mSelectedComponentId)) { + mSelectedComponentPkgName = pkgName; + mSelectedComponentId = cmpntId; + mListener.onItemClicked(pkgName, cmpntId, params); + final int count = mContent.getChildCount(); + final Resources res = getResources(); + for (int i = 0; i < count; i++) { + final View child = mContent.getChildAt(i); + final TextView tv = (TextView) child.findViewById(R.id.title); + if (tv != null) { + if (child == v) { + tv.setTextColor( + res.getColor(R.color.component_selection_current_text_color)); + } else { + tv.setTextColor(res.getColor(android.R.color.white)); + } + } + } + } + } + }; + + private class ThemesObserver extends ContentObserver { + public ThemesObserver() { + super(null); + } + + public void register() { + mContext.getContentResolver().registerContentObserver( + ThemesColumns.CONTENT_URI, false, this); + } + + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + // reload items by calling setComponentType() + if (mComponentType != null) { + final String componentType = mComponentType; + mComponentType = null; + mContent.post(new Runnable() { + @Override + public void run() { + setComponentType(componentType, mSelectedComponentPkgName); + } + }); + } + } + } + + public interface OnItemClickedListener { + public void onItemClicked(String pkgName, long componentId, Bundle params); + } + + public interface OnOpenCloseListener { + public void onSelectorOpened(); + public void onSelectorClosed(); + public void onSelectorClosing(); + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java b/src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java new file mode 100644 index 0000000..96547f3 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.SystemClock; + +/** + * An extension of LayerDrawables that is intended to cross-fade between + * the first and second layer. To start the transition, call {@link #startTransition(int)}. To + * display just the first layer, call {@link #resetTransition()}. + *

+ * It can be defined in an XML file with the <transition> element. + * Each Drawable in the transition is defined in a nested <item>. For more + * information, see the guide to Drawable Resources.

+ * + * @attr ref android.R.styleable#LayerDrawableItem_left + * @attr ref android.R.styleable#LayerDrawableItem_top + * @attr ref android.R.styleable#LayerDrawableItem_right + * @attr ref android.R.styleable#LayerDrawableItem_bottom + * @attr ref android.R.styleable#LayerDrawableItem_drawable + * @attr ref android.R.styleable#LayerDrawableItem_id + * + */ +public class IconTransitionDrawable extends LayerDrawable { + + /** + * A transition is about to start. + */ + private static final int TRANSITION_STARTING = 0; + + /** + * The transition has started and the animation is in progress + */ + private static final int TRANSITION_RUNNING = 1; + + /** + * No transition will be applied + */ + private static final int TRANSITION_NONE = 2; + + /** + * The current state of the transition. One of {@link #TRANSITION_STARTING}, + * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE} + */ + private int mTransitionState = TRANSITION_NONE; + + private long mStartTimeMillis; + private int mFrom; + private int mTo; + private int mDuration; + private int mAlpha = 0; + private float mFromScale; + private float mToScale; + + /** + * Create a new transition drawable with the specified list of layers. At least + * 2 layers are required for this drawable to work properly. + */ + public IconTransitionDrawable(Drawable[] layers) { + super(layers); + } + + /** + * Begin the second layer on top of the first layer. + * + * @param durationMillis The length of the transition in milliseconds + */ + public void startTransition(int durationMillis) { + mFrom = 0; + mTo = 255; + mAlpha = 0; + mFromScale = 0f; + mToScale = 1.0f; + mDuration = durationMillis; + mTransitionState = TRANSITION_STARTING; + invalidateSelf(); + } + + /** + * Show only the first layer. + */ + public void resetTransition() { + mAlpha = 0; + mTransitionState = TRANSITION_NONE; + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + boolean done = true; + float scale = 0f; + + switch (mTransitionState) { + case TRANSITION_STARTING: + mStartTimeMillis = SystemClock.uptimeMillis(); + done = false; + mTransitionState = TRANSITION_RUNNING; + break; + + case TRANSITION_RUNNING: + if (mStartTimeMillis >= 0) { + float normalized = (float) + (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration; + done = normalized >= 1.0f; + normalized = Math.min(normalized, 1.0f); + mAlpha = (int) (mFrom + (mTo - mFrom) * normalized); + scale = mFromScale + (mToScale - mFromScale) * normalized; + } + break; + } + + final int alpha = mAlpha; + + if (done) { + // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw + // the appropriate drawable[s] and return + if (alpha == 0) { + getDrawable(0).draw(canvas); + } + if (alpha == 0xFF) { + getDrawable(1).draw(canvas); + + } + return; + } + + Drawable d; + d = getDrawable(0); + d.setAlpha(255 - alpha); + int cx = getIntrinsicWidth() / 2; + int cy = getIntrinsicHeight() / 2; + canvas.save(); + canvas.scale(1.0f - scale, 1.0f - scale, cx, cy); + d.draw(canvas); + canvas.restore(); + d.setAlpha(0xFF); + + if (alpha > 0) { + d = getDrawable(1); + d.setAlpha(alpha); + canvas.save(); + canvas.scale(scale, scale, cx, cy); + d.draw(canvas); + canvas.restore(); + d.setAlpha(0xFF); + } + + if (!done) { + invalidateSelf(); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/MyThemeFragment.java b/src/org/cyanogenmod/theme/chooser/MyThemeFragment.java new file mode 100644 index 0000000..90a3f05 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/MyThemeFragment.java @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.MediaPlayer; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.content.Loader; +import android.util.Log; +import android.util.MutableLong; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.cyanogenmod.theme.util.AudioUtils; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.ThemedTypefaceHelper; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeChangeRequest; +import cyanogenmod.themes.ThemeChangeRequest.RequestType; +import cyanogenmod.themes.ThemeManager; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALL; + +public class MyThemeFragment extends ThemeFragment { + private static final String TAG = MyThemeFragment.class.getSimpleName(); + + private static final String ARG_BASE_THEME_PACKAGE_NAME = "baseThemePkgName"; + private static final String ARG_BASE_THEME_NAME = "baseThemeName"; + private static final String ARG_BASE_THEME_AUTHOR = "baseThemeAuthor"; + + private String mBaseThemeName; + private String mBaseThemeAuthor; + + private SurfaceView mSurfaceView; + + static MyThemeFragment newInstance(String baseThemePkgName, String baseThemeName, + String baseThemeAuthor, boolean skipLoadingAnim, + boolean animateToLockScreenCard) { + MyThemeFragment f = new MyThemeFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, CURRENTLY_APPLIED_THEME); + args.putString(ARG_BASE_THEME_PACKAGE_NAME, baseThemePkgName); + args.putString(ARG_BASE_THEME_NAME, baseThemeName); + args.putString(ARG_BASE_THEME_AUTHOR, baseThemeAuthor); + args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); + args.putBoolean(ARG_ANIMATE_TO_LOCK_SCREEN_CARD, animateToLockScreenCard); + f.setArguments(args); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Context context = getActivity(); + ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, + getAppliedFontPackageName()); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + mBaseThemePkgName = getArguments().getString(ARG_BASE_THEME_PACKAGE_NAME); + mBaseThemeName = getArguments().getString(ARG_BASE_THEME_NAME); + mBaseThemeAuthor = getArguments().getString(ARG_BASE_THEME_AUTHOR); + mShowLockScreenSelectorAfterContentLoaded = getArguments().getBoolean( + ARG_ANIMATE_TO_LOCK_SCREEN_CARD); + mSurfaceView = createSurfaceView(); + populateBaseThemeSupportedComponents(mBaseThemePkgName); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = super.onCreateView(inflater, container, savedInstanceState); + mThemeTagLayout.setAppliedTagEnabled(true); + if (mBaseThemePkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { + mThemeTagLayout.setDefaultTagEnabled(true); + } + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + mDelete.setVisibility(View.GONE); + setCustomized(isThemeCustomized()); + return v; + } + + @Override + public void onResume() { + super.onResume(); + if (!mExpanded && getLoaderManager().getLoader(0) != null) { + getLoaderManager().restartLoader(0, null, this); + } + + IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + getActivity().registerReceiver(mWallpaperChangeReceiver, filter); + } + + @Override + public void onPause() { + getActivity().unregisterReceiver(mWallpaperChangeReceiver); + super.onPause(); + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (mThemeTagLayout == null) return; + + if (!isVisibleToUser) { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + } else { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { + PreferenceUtils.removeUpdatedTheme(getActivity(), mBaseThemePkgName); + } + } + } + + @Override + protected boolean onPopupMenuItemClick(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_reset: + resetTheme(); + return true; + } + + return super.onPopupMenuItemClick(item); + } + + @Override + public void collapse(boolean applyTheme) { + super.collapse(applyTheme); + if (mSurfaceView != null) mSurfaceView.setVisibility(View.VISIBLE); + } + + @Override + public void expand() { + super.expand(); + if (mSurfaceView != null && mShadowFrame.indexOfChild(mSurfaceView) >= 0) { + mSurfaceView.setVisibility(View.GONE); + mWallpaper.setVisibility(View.INVISIBLE); + } + } + + @Override + public void performClick(boolean clickedOnContent) { + if (clickedOnContent) { + showCustomizeResetLayout(); + } else { + if (isShowingCustomizeResetLayout()) { + hideCustomizeResetLayout(); + } else { + super.performClick(clickedOnContent); + } + } + } + + @Override + public void setCurrentTheme(Map currentTheme, + MutableLong currentWallpaperComponentId) { + super.setCurrentTheme(currentTheme, currentWallpaperComponentId); + for (String key : currentTheme.keySet()) { + mSelectedComponentsMap.put(key, currentTheme.get(key)); + } + mSelectedWallpaperComponentId = currentWallpaperComponentId.value; + } + + @Override + public boolean componentsChanged() { + // If an external wallpaper/ls are set then something changed! + if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; + + for (String key : mSelectedComponentsMap.keySet()) { + String current = mCurrentTheme.get(key); + if (current == null || !current.equals(mSelectedComponentsMap.get(key))) { + return true; + } + if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && + mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { + return true; + } + } + return false; + } + + @Override + protected void applyThemeWhenPopulated(String pkgName, List components) { + super.applyThemeWhenPopulated(pkgName, components); + populateComponentsToApply(pkgName, components); + } + + private void populateComponentsToApply(String pkgName, List components) { + String selection = ThemesColumns.PKG_NAME + "=?"; + String[] selectionArgs = { pkgName }; + Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, + null, selection, selectionArgs, null); + if (c != null) { + if (c.getCount() > 0 && c.moveToFirst()) { + mSelectedComponentsMap.clear(); + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ALARMS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ALARMS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_BOOT_ANIM)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_BOOT_ANIM, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_FONTS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_FONTS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ICONS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ICONS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LAUNCHER)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LOCKSCREEN)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NAVIGATION_BAR)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NOTIFICATIONS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NOTIFICATIONS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_OVERLAYS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_OVERLAYS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_RINGTONES)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_RINGTONES, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_STATUS_BAR)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_STATUS_BAR, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, pkgName); + } + } + c.close(); + } + + // strip out any components that are not in the components list + if (components != null) { + Iterator> iterator = + mSelectedComponentsMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (!components.contains(entry.getKey())) { + iterator.remove(); + } + } + } + } + + private void loadComponentsToApply() { + for (String component : mSelectedComponentsMap.keySet()) { + loadComponentFromPackage(mSelectedComponentsMap.get(component), component, + mSelectedWallpaperComponentId); + } + } + + private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // only update if we are the current visible fragment or if there is no theme + // being applied. + ThemeManager tm = getThemeManager(); + if (!tm.isThemeApplying() || getUserVisibleHint()) { + final WallpaperManager wm = WallpaperManager.getInstance(context); + if (wm.getWallpaperInfo() != null) { + addSurfaceView(mSurfaceView); + } else { + removeSurfaceView(mSurfaceView); + } + + Drawable wp = context == null ? null : wm.getDrawable(); + if (wp != null) { + mWallpaper.setImageDrawable(wp); + mWallpaperCard.setWallpaper(wp); + } + } + } + }; + + private void setCustomized(boolean customized) { + mReset.setVisibility(customized ? View.VISIBLE : View.GONE); + mThemeTagLayout.setCustomizedTagEnabled(customized); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_ALL: + if (args != null) { + String pkgName = args.getString(ARG_PACKAGE_NAME); + if (pkgName != null) { + return super.onCreateLoader(id, args); + } + } + return CursorLoaderHelper.myThemeFragmentCursorLoader(getActivity(), id); + default: + // Only LOADER_ID_ALL differs for MyThemeFragment + return super.onCreateLoader(id, args); + } + } + + @Override + public void onLoadFinished(Loader loader, Cursor c) { + super.onLoadFinished(loader, c); + // if the theme is resetting, we need to apply these changes now that the supported + // theme components have been properly set. + if (loader.getId() == LOADER_ID_ALL) { + if (mThemeResetting) { + applyTheme(); + } else if (mApplyThemeOnPopulated) { + loadComponentsToApply(); + applyTheme(); + } else if (mSelectedComponentsMap.size() == 0) { + //Re-populates selected components with current theme. Why? + //We got here because the cursor was reloaded after the user pressed back and no + //changes were applied, causing the selected components map to be wiped out + mSelectedComponentsMap.putAll(mCurrentTheme); + } + } + } + + @Override + protected Map fillMissingComponentsWithDefault( + Map originalMap) { + // Only the ThemeFragment should be altering this, for the MyThemeFragment this is not + // desirable as it changes components the user did not even touch. + return originalMap; + } + + @Override + protected ThemeChangeRequest getThemeChangeRequestForComponents( + Map componentMap) { + return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST_MIXNMATCH); + } + + @Override + protected Map getComponentsToApply() { + Map componentsToApply = mThemeResetting + ? getEmptyComponentsMap() + : new HashMap(); + if (mThemeResetting) { + final String pkgName = getThemePackageName(); + for (String component : mBaseThemeSupportedComponents) { + componentsToApply.put(component, pkgName); + } + } else { + // Only apply components that actually changed + for (String component : mSelectedComponentsMap.keySet()) { + String currentPkg = mCurrentTheme.get(component); + String selectedPkg = mSelectedComponentsMap.get(component); + if (currentPkg == null || mThemeResetting || !currentPkg.equals(selectedPkg) || + mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { + componentsToApply.put(component, selectedPkg); + } + } + if (mExternalLockscreenUri != null) { + if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { + componentsToApply.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); + } + if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { + componentsToApply.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); + } + } + } + return componentsToApply; + } + + @Override + protected void populateSupportedComponents(Cursor c) { + } + + @Override + protected Boolean shouldShowComponentCard(String component) { + return true; + } + + @Override + protected void loadTitle(Cursor c) { + mTitle.setText(mBaseThemeName); + mAuthor.setText(mBaseThemeAuthor); + } + + @Override + protected void loadWallpaper(Cursor c, boolean animate) { + mExternalWallpaperUri = null; + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadWallpaper(c, animate); + return; + } + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mWallpaperCard, true); + } + + int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); + final Resources res = getResources(); + final Context context = getActivity(); + final WallpaperManager wm = WallpaperManager.getInstance(context); + if (wm.getWallpaperInfo() != null) { + addSurfaceView(mSurfaceView); + } else { + removeSurfaceView(mSurfaceView); + } + + Drawable wp = context == null ? null : wm.getDrawable(); + if (wp == null) { + Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); + if (bmp != null) wp = new BitmapDrawable(res, bmp); + } + if (wp != null) { + mWallpaper.setImageDrawable(wp); + mWallpaperCard.setWallpaper(wp); + setCardTitle(mWallpaperCard, mCurrentTheme.get(ThemesColumns.MODIFIES_LAUNCHER), + getString(R.string.wallpaper_label)); + } else { + mWallpaperCard.clearWallpaper(); + mWallpaperCard.setEmptyViewEnabled(true); + setAddComponentTitle(mWallpaperCard, getString(R.string.wallpaper_label)); + } + + if (animate) { + animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); + } + } + + @Override + protected void loadLockScreen(Cursor c, boolean animate) { + mExternalLockscreenUri = null; + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadLockScreen(c, animate); + return; + } + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mLockScreenCard, true); + } + + //If the current theme includes a lock wallpaper, the WallpaperMgr will + //return a valid Drawable we can display in the card. However, if the user + //picked a LLS, we need to get the path from the provider and manually load the bitmap + int wpIdx = c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW); + Drawable wp = null; + if (wpIdx >= 0) { + final Resources res = getResources(); + Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); + if (bmp != null) wp = new BitmapDrawable(res, bmp); + } else { + final Context context = getActivity(); + wp = context == null ? null : + WallpaperManager.getInstance(context).getFastKeyguardDrawable(); + } + if (wp != null) { + mLockScreenCard.setWallpaper(wp); + } else if (!mSelectedComponentsMap.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { + mLockScreenCard.clearWallpaper(); + mLockScreenCard.setEmptyViewEnabled(true); + setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); + } + + if (animate) { + animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); + } + } + + @Override + protected void loadFont(Cursor c, boolean animate) { + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadFont(c, animate); + return; + } + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mFontPreview, true); + } + setCardTitle(mFontCard, mCurrentTheme.get(ThemesColumns.MODIFIES_FONTS), + getString(R.string.font_label)); + + TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); + ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), + getAppliedFontPackageName()); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + mFontPreview.setTypeface(mTypefaceNormal); + if (animate) { + animateContentChange(R.id.font_preview_container, mFontPreview, overlay); + } + } + + @Override + protected void loadAudible(int type, Cursor c, boolean animate) { + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadAudible(type, c, animate); + return; + } + ComponentCardView audibleContainer = null; + ImageView playPause = null; + String modsComponent = ""; + switch (type) { + case RingtoneManager.TYPE_RINGTONE: + audibleContainer = mRingtoneCard; + playPause = mRingtonePlayPause; + modsComponent = ThemesColumns.MODIFIES_RINGTONES; + break; + case RingtoneManager.TYPE_NOTIFICATION: + audibleContainer = mNotificationCard; + playPause = mNotificationPlayPause; + modsComponent = ThemesColumns.MODIFIES_NOTIFICATIONS; + break; + case RingtoneManager.TYPE_ALARM: + audibleContainer = mAlarmCard; + playPause = mAlarmPlayPause; + modsComponent = ThemesColumns.MODIFIES_ALARMS; + break; + } + if (audibleContainer == null) return; + + if (playPause == null) { + playPause = + (ImageView) audibleContainer.findViewById(R.id.play_pause); + } + TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); + MediaPlayer mp = mMediaPlayers.get(playPause); + if (mp == null) { + mp = new MediaPlayer(); + } + + final Context context = getActivity(); + Ringtone ringtone = null; + try { + Uri ringtoneUri = AudioUtils.loadDefaultAudible(context, type, mp); + if (ringtoneUri != null) ringtone = RingtoneManager.getRingtone(context, ringtoneUri); + } catch (IOException e) { + Log.w(TAG, "Unable to load default sound ", e); + } + + if (ringtone != null) { + title.setText(ringtone.getTitle(context)); + setCardTitle(audibleContainer, mCurrentTheme.get(modsComponent), + getAudibleLabel(type)); + } else { + title.setText(getString(R.string.audible_title_none)); + setAddComponentTitle(audibleContainer, getAudibleLabel(type)); + playPause.setVisibility(View.INVISIBLE); + audibleContainer.setEmptyViewEnabled(true); + } + + playPause.setTag(mp); + mMediaPlayers.put(playPause, mp); + playPause.setOnClickListener(mPlayPauseClickListener); + mp.setOnCompletionListener(mPlayCompletionListener); + } + + @Override + protected void loadStatusBar(Cursor c, boolean animate) { + super.loadStatusBar(c, animate); + setCardTitle(mStatusBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_STATUS_BAR), + getString(R.string.statusbar_label)); + } + + @Override + protected void loadIcons(Cursor c, boolean animate) { + super.loadIcons(c, animate); + setCardTitle(mIconCard, mCurrentTheme.get(ThemesColumns.MODIFIES_ICONS), + getString(R.string.icon_label)); + } + + @Override + protected void loadNavBar(Cursor c, boolean animate) { + super.loadNavBar(c, animate); + setCardTitle(mNavBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_NAVIGATION_BAR), + getString(R.string.navbar_label)); + } + + @Override + protected void loadStyle(Cursor c, boolean animate) { + super.loadStyle(c, animate); + setCardTitle(mStyleCard, mCurrentTheme.get(ThemesColumns.MODIFIES_OVERLAYS), + getString(R.string.style_label)); + } + + @Override + protected void loadBootAnimation(Cursor c) { + super.loadBootAnimation(c); + setCardTitle(mBootAnimationCard, mCurrentTheme.get(ThemesColumns.MODIFIES_BOOT_ANIM), + getString(R.string.boot_animation_label)); + } + + @Override + public String getThemePackageName() { + if (mBaseThemePkgName == null) { + // check if the package name is defined in the arguments bundle + Bundle bundle = getArguments(); + if (bundle != null) { + mBaseThemePkgName = bundle.getString(ARG_BASE_THEME_PACKAGE_NAME); + } + } + return mBaseThemePkgName; + } + + private SurfaceView createSurfaceView() { + final Context context = getActivity(); + if (context == null) return null; + + SurfaceView sv = new SurfaceView(context); + final Resources res = context.getResources(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + res.getDimensionPixelSize(R.dimen.wallpaper_preview_width), + res.getDimensionPixelSize(R.dimen.theme_preview_height), + Gravity.CENTER_HORIZONTAL); + sv.setLayoutParams(params); + + return sv; + } + + private void addSurfaceView(SurfaceView sv) { + if (mShadowFrame.indexOfChild(mSurfaceView) < 0) { + int idx = mShadowFrame.indexOfChild(mWallpaper); + mShadowFrame.addView(sv, idx + 1); + } + } + + private void removeSurfaceView(SurfaceView sv) { + if (mShadowFrame.indexOfChild(mSurfaceView) >= 0) { + mShadowFrame.removeView(sv); + } + } + + /** + * Populates mBaseThemeSupportedComponents. + * @param pkgName Package name of the base theme used + */ + private void populateBaseThemeSupportedComponents(String pkgName) { + String selection = ThemesColumns.PKG_NAME + "=?"; + String[] selectionArgs = { pkgName }; + Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, + null, selection, selectionArgs, null); + if (c != null) { + if (c.moveToFirst()) { + List components = ThemeUtils.getAllComponents(); + final String baseThemePackageName = getThemePackageName(); + for (String component : components) { + int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int modifiesCompIdx = c.getColumnIndex(component); + + String pkg = pkgIdx >= 0 ? c.getString(pkgIdx) : null; + boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); + if (supported && baseThemePackageName.equals(pkg)) { + mBaseThemeSupportedComponents.add(component); + } + } + } + c.close(); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java b/src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java new file mode 100644 index 0000000..cefc344 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.PagerAdapter; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Implementation of {@link android.support.v4.view.PagerAdapter} that + * uses a {@link android.support.v4.app.Fragment} to manage each page. This class also handles + * saving and restoring of fragment's state. + * + *

This version of the pager is more useful when there are a large number + * of pages, working more like a list view. When pages are not visible to + * the user, their entire fragment may be destroyed, only keeping the saved + * state of that fragment. This allows the pager to hold on to much less + * memory associated with each visited page as compared to + * {@link android.support.v4.app.FragmentPagerAdapter} at the cost of potentially more overhead when + * switching between pages. + * + *

When using FragmentPagerAdapter the host ViewPager must have a + * valid ID set.

+ * + *

Subclasses only need to implement {@link #getItem(int)} + * and {@link #getCount()} to have a working adapter. + * + *

Here is an example implementation of a pager containing fragments of + * lists: + * + * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java + * complete} + * + *

The R.layout.fragment_pager resource of the top-level fragment is: + * + * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml + * complete} + * + *

The R.layout.fragment_pager_list resource containing each + * individual fragment's layout is: + * + * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml + * complete} + */ +public abstract class NewFragmentStatePagerAdapter extends PagerAdapter { + private static final String TAG = NewFragmentStatePagerAdapter.class.getSimpleName(); + private static final boolean DEBUG = false; + + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + + private long[] mItemIds = new long[] {}; + private ArrayList mSavedState = new ArrayList(); + private ArrayList mFragments = new ArrayList(); + private Fragment mCurrentPrimaryItem = null; + + public NewFragmentStatePagerAdapter(FragmentManager fm) { + + mFragmentManager = fm; + createIdCache(); + } + + /** + * Return the Fragment associated with a specified position. + */ + public abstract Fragment getItem(int position); + + /** + * Return a unique identifier for the item at the given position. + */ + public abstract long getItemId(int position); + + @Override + public void startUpdate(ViewGroup container) { + } + + private void checkForIdChanges() { + long[] newItemIds = new long[getCount()]; + for (int i = 0; i < newItemIds.length; i++) { + newItemIds[i] = getItemId(i); + } + + if (!Arrays.equals(mItemIds, newItemIds)) { + ArrayList newSavedState = new ArrayList(); + ArrayList newFragments = new ArrayList(); + + for (int oldPosition = 0; oldPosition < mItemIds.length; oldPosition++) { + int newPosition = POSITION_NONE; + for (int i = 0; i < newItemIds.length; i++) { + if (mItemIds[oldPosition] == newItemIds[i]) { + newPosition = i; + break; + } + } + if (newPosition >= 0) { + if (oldPosition < mSavedState.size()) { + Fragment.SavedState savedState = mSavedState.get(oldPosition); + if (savedState != null) { + while (newSavedState.size() <= newPosition) { + newSavedState.add(null); + } + newSavedState.set(newPosition, savedState); + } + } + if (oldPosition < mFragments.size()) { + Fragment fragment = mFragments.get(oldPosition); + if (fragment != null) { + while (newFragments.size() <= newPosition) { + newFragments.add(null); + } + newFragments.set(newPosition, fragment); + } + } + } + } + + mItemIds = newItemIds; + mSavedState = newSavedState; + mFragments = newFragments; + } + } + + @Override + public void notifyDataSetChanged() { + checkForIdChanges(); + + super.notifyDataSetChanged(); + } + + /** + * Create the initial set of item IDs. Run this after you have set your adapter data. + */ + public void createIdCache() { + // If we have already stored ids, don't overwrite them + if (mItemIds.length == 0) { + // getCount might have overhead, so run it as late as possible + final int count = getCount(); + if (count > 0) { + mItemIds = new long[count]; + for (int i = 0; i < count; i++) { + mItemIds[i] = getItemId(i); + } + } + } + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + + createIdCache(); + + // If we already have this item instantiated, there is nothing + // to do. This can happen when we are restoring the entire pager + // from its saved state, where the fragment manager has already + // taken care of restoring the fragments we previously had instantiated. + if (mFragments.size() > position) { + Fragment f = mFragments.get(position); + if (f != null) { + return f; + } + } + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); + if (mSavedState.size() > position) { + Fragment.SavedState fss = mSavedState.get(position); + if (fss != null) { + fragment.setInitialSavedState(fss); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + mFragments.set(position, fragment); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + + " v=" + ((Fragment)object).getView()); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + + // Only set the position to null if the fragment being removed is at "position" + // We do this because checkForIdChanges updates the mFragments list and if a fragment + // was removed then the fragment at "position" is not the fragment that was removed. + if (position < mFragments.size() && fragment == mFragments.get(position)) { + mFragments.set(position, null); + } + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment)object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; + + mItemIds = new long[getCount()]; + for (int i = 0; i < mItemIds.length; i++) { + mItemIds[i] = getItemId(i); + } + if (mSavedState.size() > 0) { + state = new Bundle(); + + if (mItemIds.length > 0) { + state.putLongArray("itemids", mItemIds); + } + + Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; + mSavedState.toArray(fss); + state.putParcelableArray("states", fss); + } + for (int i=0; i keys = bundle.keySet(); + for (String key: keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); + } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } + } + } + checkForIdChanges(); + } + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java b/src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java new file mode 100644 index 0000000..dddd8f5 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; + +public class NotificationHijackingService extends NotificationListenerService { + private static final String TAG = NotificationHijackingService.class.getName(); + private static final String GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"; + private static final String ACTION_INSTALLED = + "com.android.vending.SUCCESSFULLY_INSTALLED_CLICKED"; + private static final String EXTRA_PACKAGE_NAME = "package_name"; + + @Override + public void onNotificationPosted(StatusBarNotification sbn) { + if (GOOGLE_PLAY_PACKAGE_NAME.equals(sbn.getPackageName())) { + PendingIntent contentIntent = sbn.getNotification().contentIntent; + if (contentIntent == null) return; + Intent intent = contentIntent.getIntent(); + if (intent == null) return; + String action = intent.getAction(); + if (ACTION_INSTALLED.equals(action)) { + String pkgName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + try { + PackageInfo pi = getPackageManager().getPackageInfo(pkgName, 0); + if (pi != null) { + if (pi.themeInfo != null) { + cancelNotification(sbn.getKey()); + } + } + } catch (PackageManager.NameNotFoundException e) { + } + } + } + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn) { + } + + // ensure that this notification listener is enabled. + // the service watches for google play notifications + public static void ensureEnabled(Context context) { + ComponentName me = new ComponentName(context, NotificationHijackingService.class); + String meFlattened = me.flattenToString(); + + String existingListeners = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + + if (!TextUtils.isEmpty(existingListeners)) { + if (existingListeners.contains(meFlattened)) { + return; + } else { + existingListeners += ":" + meFlattened; + } + } else { + existingListeners = meFlattened; + } + + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + existingListeners); + } +} diff --git a/src/org/cyanogenmod/theme/chooser/PagerContainer.java b/src/org/cyanogenmod/theme/chooser/PagerContainer.java new file mode 100644 index 0000000..852b847 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/PagerContainer.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.content.Context; +import android.graphics.Point; +import android.support.v4.view.ThemeViewPager; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +/** + * PagerContainer: A layout that displays a ViewPager with its children that are outside + * the typical pager bounds. + */ +public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener { + private static final int ANIMATE_OUT_DURATION = 300; + private static final int ANIMATE_OUT_INTERPOLATE_FACTOR = 1; + private static final int ANIMATE_IN_DURATION = 300; + private static final int ANIMATE_IN_INTERPOLATE_FACTOR = 2; + + private ThemeViewPager mPager; + private Point mCenter = new Point(); + private Point mInitialTouch = new Point(); + private int mCollapsedHeight; + private boolean mIsAnimating = false; + + boolean mNeedsRedraw = false; + + public PagerContainer(Context context) { + this(context, null, 0); + } + + public PagerContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagerContainer(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mCollapsedHeight = generateLayoutParams(attrs).height; + + //Disable clipping of children so non-selected pages are visible + setClipChildren(false); + } + + @Override + protected void onFinishInflate() { + try { + mPager = (ThemeViewPager) getChildAt(0); + mPager.setOnPageChangeListener(this); + } catch (Exception e) { + throw new IllegalStateException("The root child of PagerContainer must be a ViewPager"); + } + } + + public ThemeViewPager getViewPager() { + return mPager; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mCenter.x = w / 2; + mCenter.y = h / 2; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mIsAnimating) return true; + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Do not allow touch events to propagate if we are animating + if (mIsAnimating) return true; + + //We capture any touches not already handled by the ViewPager + // to implement scrolling from a touch outside the pager bounds. + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mInitialTouch.x = (int)ev.getX(); + mInitialTouch.y = (int)ev.getY(); + default: + ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y); + break; + } + + return mPager.dispatchTouchEvent(ev); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + //Force the container to redraw on scrolling. + //Without this the outer pages render initially and then stay static + if (mNeedsRedraw) invalidate(); + } + + @Override + public void onPageSelected(int position) { } + + @Override + public void onPageScrollStateChanged(int state) { + mNeedsRedraw = (state != ThemeViewPager.SCROLL_STATE_IDLE); + } + + public void setIsAnimating(boolean isAnimating) { + mIsAnimating = isAnimating; + } + + public void expand() { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); + params.height = LinearLayout.LayoutParams.MATCH_PARENT; + setLayoutParams(params); + + mPager.setExpanded(true); + + final int current = mPager.getCurrentItem(); + final int prevY = (int) getY(); + + //Since our viewpager's width is changing to fill the screen + //we must start the left/right children of the current page inwards on first draw + final int lChildPrevXf; + final int rChildPrevXf; + + if (current != 0) { + final View lchild = mPager.getViewForPosition(current - 1); + lChildPrevXf = (int) lchild.getX(); + } else { + lChildPrevXf = 0; + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rChildPrevXf = (int) rchild.getX(); + } else { + rChildPrevXf = 0; + } + + + final ViewTreeObserver observer = mPager.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + if (current != 0) { + View lchild = mPager.getViewForPosition(current - 1); + lchild.setTranslationY(prevY - getY()); + lchild.setX(lChildPrevXf); + animateChildOut(lchild, -getWidth()); + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rchild.setX(rChildPrevXf); + rchild.setTranslationY(prevY - getY()); + animateChildOut(rchild, getWidth()); + } + return false; + } + }); + } + + public void collapse() { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); + params.height = mCollapsedHeight; + setLayoutParams(params); + + mPager.setExpanded(false); + int current = mPager.getCurrentItem(); + final int prevY = (int) getY(); + + if (current != 0) { + View lchild = mPager.getViewForPosition(current - 1); + lchild.setTranslationY(0); + animateChildIn(lchild); + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rchild.setTranslationY(0); + animateChildIn(rchild); + } + } + + private void animateChildOut(final View v, float endX) { + v.animate() + .translationX(endX) + .setDuration(ANIMATE_OUT_DURATION) + .setInterpolator(new AccelerateInterpolator(ANIMATE_OUT_INTERPOLATE_FACTOR)); + } + + private void animateChildIn(final View v) { + v.animate() + .translationX(0) + .setDuration(ANIMATE_IN_DURATION) + .setInterpolator(new DecelerateInterpolator(ANIMATE_IN_INTERPOLATE_FACTOR)); + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooser/ThemeFragment.java b/src/org/cyanogenmod/theme/chooser/ThemeFragment.java new file mode 100644 index 0000000..d0891a7 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ThemeFragment.java @@ -0,0 +1,3059 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.IntEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.WallpaperManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.FileUtils; +import android.os.Handler; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.text.TextUtils; +import android.util.Log; +import android.util.MutableLong; +import android.util.SparseArray; +import android.view.Display; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.ScaleAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.PopupMenu; +import android.widget.ProgressBar; +import android.widget.Space; +import android.widget.TextView; +import android.widget.Toast; + +import org.cyanogenmod.theme.chooser.ComponentSelector.OnItemClickedListener; +import org.cyanogenmod.theme.util.AudioUtils; +import org.cyanogenmod.theme.util.BootAnimationHelper; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.IconPreviewHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.ThemedTypefaceHelper; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; +import org.cyanogenmod.theme.util.WallpaperUtils; +import org.cyanogenmod.theme.widget.BootAniImageView; +import org.cyanogenmod.theme.widget.ConfirmCancelOverlay; +import org.cyanogenmod.theme.widget.LockableScrollView; +import org.cyanogenmod.theme.widget.ThemeTagLayout; + +import cyanogenmod.app.ThemeVersion; +import cyanogenmod.providers.CMSettings; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeChangeRequest; +import cyanogenmod.themes.ThemeChangeRequest.RequestType; +import cyanogenmod.themes.ThemeManager; + +import org.cyanogenmod.internal.util.CmLockPatternUtils; +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; + +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; + +import static org.cyanogenmod.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; + +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_INVALID; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALL; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_FONT; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_LIVE_LOCK_SCREEN; + +import static cyanogenmod.providers.CMSettings.Secure.LIVE_LOCK_SCREEN_ENABLED; + +import static org.cyanogenmod.internal.util.ThemeUtils.SYSTEM_TARGET_API; + +public class ThemeFragment extends Fragment implements LoaderManager.LoaderCallbacks, + ThemeManager.ThemeChangeListener, ThemeManager.ThemeProcessingListener { + private static final String TAG = ThemeFragment.class.getSimpleName(); + + public static final int ANIMATE_START_DELAY = 200; + public static final int ANIMATE_DURATION = 300; + public static final int ANIMATE_INTERPOLATE_FACTOR = 3; + public static final int ANIMATE_COMPONENT_CHANGE_DURATION = 200; + public static final int ANIMATE_COMPONENT_ICON_DELAY = 50; + public static final int ANIMATE_PROGRESS_IN_DURATION = 500; + public static final int ANIMATE_TITLE_OUT_DURATION = 400; + public static final int ANIMATE_PROGRESS_OUT_DURATION = 400; + public static final int ANIMATE_TITLE_IN_DURATION = 500; + public static final int ANIMATE_APPLY_LAYOUT_DURATION = 300; + + public static final String CURRENTLY_APPLIED_THEME = "currently_applied_theme"; + + private static final ComponentName COMPONENT_DIALER = + new ComponentName("com.android.dialer", "com.android.dialer.DialtactsActivity"); + private static final ComponentName COMPONENT_DIALERNEXT = + new ComponentName("com.cyngn.dialer", "com.android.dialer.DialtactsActivity"); + private static final ComponentName COMPONENT_MESSAGING = + new ComponentName("com.android.mms", "com.android.mms.ui.ConversationList"); + private static final ComponentName COMPONENT_CAMERANEXT = + new ComponentName("com.cyngn.cameranext", "com.android.camera.CameraLauncher"); + private static final ComponentName COMPONENT_CAMERA = + new ComponentName("com.android.camera2", "com.android.camera.CameraActivity"); + private static final ComponentName COMPONENT_BROWSER = + new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"); + private static final ComponentName COMPONENT_SETTINGS = + new ComponentName("com.android.settings", "com.android.settings.Settings"); + private static final ComponentName COMPONENT_CALENDAR = + new ComponentName("com.android.calendar", "com.android.calendar.AllInOneActivity"); + private static final ComponentName COMPONENT_GALERY = + new ComponentName("com.android.gallery3d", "com.android.gallery3d.app.GalleryActivity"); + + private static final String CAMERA_NEXT_PACKAGE = "com.cyngn.cameranext"; + private static final String DIALER_NEXT_PACKAGE = "com.cyngn.dialer"; + + private static final int ADDITIONAL_CONTENT_SPACE_ID = 123456; + private static final long SLIDE_CONTENT_ANIM_DURATION = 300L; + private static final long LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION = 400; + private static final long SHOW_LOCK_SCREEN_CARD_DELAY = 500; + + private static final int DEFAULT_WIFI_MARGIN = 0; + private static final int DEFAULT_CLOCK_COLOR = Color.WHITE; + + protected static final String WALLPAPER_NONE = ""; + protected static final String LOCKSCREEN_NONE = ""; + + protected static final String ARG_PACKAGE_NAME = "pkgName"; + protected static final String ARG_COMPONENT_ID = "cmpntId"; + protected static final String ARG_SKIP_LOADING_ANIM = "skipLoadingAnim"; + protected static final String ARG_ANIMATE_TO_LOCK_SCREEN_CARD = "animateToLockScreenCard"; + + private static final String LLS_PACKAGE_NAME = "com.cyngn.lockscreen.live"; + private static final String LLS_PROVIDER_NAME = + "com.cyngn.lockscreen.live.LockScreenProviderService"; + + private static final int PERMISSION_REQUEST = 100; + + protected static ComponentName[] sIconComponents; + + protected static TypefaceHelperCache sTypefaceHelperCache; + + /** + * Maps the card's resource ID to a theme component + */ + private final SparseArray mCardIdsToComponentTypes = new SparseArray(); + + protected String mPkgName; + protected Typeface mTypefaceNormal; + protected int mBatteryStyle; + + protected LockableScrollView mScrollView; + protected ViewGroup mScrollContent; + protected ViewGroup mPreviewContent; // Contains icons, font, nav/status etc. Not wallpaper + protected View mLoadingView; + + //Status Bar Views + protected ComponentCardView mStatusBarCard; + protected ImageView mBluetooth; + protected ImageView mWifi; + protected ImageView mSignal; + protected ImageView mBattery; + protected TextView mClock; + + // Other Misc Preview Views + protected FrameLayout mShadowFrame; + protected ImageView mWallpaper; + protected ViewGroup mStatusBar; + protected TextView mFontPreview; + protected ComponentCardView mStyleCard; + protected ComponentCardView mFontCard; + protected ComponentCardView mIconCard; + protected ComponentCardView mBootAnimationCard; + protected BootAniImageView mBootAnimation; + + // Nav Bar Views + protected ComponentCardView mNavBarCard; + protected ViewGroup mNavBar; + protected ImageView mBackButton; + protected ImageView mHomeButton; + protected ImageView mRecentButton; + + // Title Card Views + protected ViewGroup mTitleCard; + protected ViewGroup mTitleLayout; + protected TextView mTitle; + protected TextView mAuthor; + protected ImageView mCustomize; + protected ImageView mOverflow; + protected ImageView mDelete; + protected ImageView mReset; + protected ProgressBar mProgress; + + // Additional Card Views + protected LinearLayout mAdditionalCards; + protected WallpaperCardView mWallpaperCard; + protected WallpaperCardView mLockScreenCard; + + // Style views + protected ImageView mStylePreview; + + // Sound cards + protected ComponentCardView mRingtoneCard; + protected ImageView mRingtonePlayPause; + protected ComponentCardView mNotificationCard; + protected ImageView mNotificationPlayPause; + protected ComponentCardView mAlarmCard; + protected ImageView mAlarmPlayPause; + protected Map mMediaPlayers; + + protected Handler mHandler; + + protected int mActiveCardId = -1; + protected ComponentSelector mSelector; + // Supported components for the theme this fragment represents + protected Map mSelectedComponentsMap = new HashMap(); + protected Long mSelectedWallpaperComponentId; + // Current system theme configuration as component -> pkgName + protected Map mCurrentTheme = new HashMap(); + protected MutableLong mCurrentWallpaperComponentId = new MutableLong(DEFAULT_COMPONENT_ID); + // Set of components available in the base theme + protected HashSet mBaseThemeSupportedComponents = new HashSet(); + protected Cursor mCurrentCursor; + protected int mCurrentLoaderId; + protected boolean mThemeResetting; + protected boolean mSkipLoadingAnim; + + // Accept/Cancel overlay + protected ConfirmCancelOverlay mConfirmCancelOverlay; + + // Customize/Reset theme layout + protected View mCustomizeResetLayout; + protected View mResetButton; + protected View mCustomizeButton; + protected View mDismissButton; + + // Processing theme layout + protected View mProcessingThemeLayout; + + protected ThemeTagLayout mThemeTagLayout; + + protected View mClickableView; + protected String mBaseThemePkgName; + + protected Uri mExternalWallpaperUri; + protected Uri mExternalLockscreenUri; + + protected boolean mExpanded; + protected boolean mProcessingResources; + protected boolean mApplyThemeOnPopulated; + + protected boolean mIsLegacyTheme; + + private Runnable mAfterPermissionGrantedRunnable; + + private static final int mThemeVersion = ThemeVersion.getVersion(); + + protected boolean mShowLockScreenSelectorAfterContentLoaded; + + protected enum CustomizeResetAction { + Customize, + Reset, + Dismiss + } + + static ThemeFragment newInstance(String pkgName, boolean skipLoadingAnim) { + ThemeFragment f = new ThemeFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, pkgName); + args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); + args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); + f.setArguments(args); + return f; + } + + /** + * When creating, retrieve this instance's number from its arguments. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Context context = getActivity(); + mPkgName = getArguments().getString(ARG_PACKAGE_NAME); + mSkipLoadingAnim = getArguments().getBoolean(ARG_SKIP_LOADING_ANIM); + // TODO: Load from settings once available + mBatteryStyle = 0;/*Settings.System.getInt(context.getContentResolver(), + Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ + + getIconComponents(context); + if (sTypefaceHelperCache == null) { + sTypefaceHelperCache = TypefaceHelperCache.getInstance(); + } + ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, mPkgName); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + + mHandler = new Handler(); + + mCardIdsToComponentTypes.put(R.id.status_bar_container, MODIFIES_STATUS_BAR); + mCardIdsToComponentTypes.put(R.id.font_preview_container, MODIFIES_FONTS); + mCardIdsToComponentTypes.put(R.id.icon_container, MODIFIES_ICONS); + mCardIdsToComponentTypes.put(R.id.navigation_bar_container, MODIFIES_NAVIGATION_BAR); + mCardIdsToComponentTypes.put(R.id.wallpaper_card, MODIFIES_LAUNCHER); + mCardIdsToComponentTypes.put(R.id.lockscreen_card, MODIFIES_LOCKSCREEN); + mCardIdsToComponentTypes.put(R.id.style_card, MODIFIES_OVERLAYS); + mCardIdsToComponentTypes.put(R.id.bootani_preview_container, MODIFIES_BOOT_ANIM); + mCardIdsToComponentTypes.put(R.id.ringtone_preview_container, MODIFIES_RINGTONES); + mCardIdsToComponentTypes.put(R.id.notification_preview_container, MODIFIES_NOTIFICATIONS); + mCardIdsToComponentTypes.put(R.id.alarm_preview_container, MODIFIES_ALARMS); + + mMediaPlayers = new HashMap(3); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_pager_list, container, false); + + mScrollView = (LockableScrollView) v.findViewById(android.R.id.list); + mScrollView.setScrollingEnabled(false); + mScrollContent = (ViewGroup) mScrollView.getChildAt(0); + mPreviewContent = (ViewGroup) v.findViewById(R.id.preview_container); + mLoadingView = v.findViewById(R.id.loading_view); + mThemeTagLayout = (ThemeTagLayout) v.findViewById(R.id.tag_layout); + + // Status Bar + mStatusBarCard = (ComponentCardView) v.findViewById(R.id.status_bar_container); + mStatusBar = (ViewGroup) v.findViewById(R.id.status_bar); + mBluetooth = (ImageView) v.findViewById(R.id.bluetooth_icon); + mWifi = (ImageView) v.findViewById(R.id.wifi_icon); + mSignal = (ImageView) v.findViewById(R.id.signal_icon); + mBattery = (ImageView) v.findViewById(R.id.battery); + mClock = (TextView) v.findViewById(R.id.clock); + + // Wallpaper / Font / Icons / etc + mWallpaper = (ImageView) v.findViewById(R.id.wallpaper); + mFontCard = (ComponentCardView) v.findViewById(R.id.font_preview_container); + mFontPreview = (TextView) v.findViewById(R.id.font_preview); + mFontPreview.setTypeface(mTypefaceNormal); + mIconCard = (ComponentCardView) v.findViewById(R.id.icon_container); + mShadowFrame = (FrameLayout) v.findViewById(R.id.shadow_frame); + mStyleCard = (ComponentCardView) v.findViewById(R.id.style_card); + mStylePreview = (ImageView) v.findViewById(R.id.style_preview); + mBootAnimationCard = (ComponentCardView) v.findViewById(R.id.bootani_preview_container); + mBootAnimation = + (BootAniImageView) mBootAnimationCard.findViewById(R.id.bootani_preview); + mRingtoneCard = (ComponentCardView) v.findViewById(R.id.ringtone_preview_container); + mRingtonePlayPause = (ImageView) mRingtoneCard.findViewById(R.id.play_pause); + mNotificationCard = (ComponentCardView) v.findViewById(R.id.notification_preview_container); + mNotificationPlayPause = (ImageView) mNotificationCard.findViewById(R.id.play_pause); + mAlarmCard = (ComponentCardView) v.findViewById(R.id.alarm_preview_container); + mAlarmPlayPause = (ImageView) mAlarmCard.findViewById(R.id.play_pause); + + // Nav Bar + mNavBarCard = (ComponentCardView) v.findViewById(R.id.navigation_bar_container); + mNavBar = (ViewGroup) v.findViewById(R.id.navigation_bar); + mBackButton = (ImageView) v.findViewById(R.id.back_button); + mHomeButton = (ImageView) v.findViewById(R.id.home_button); + mRecentButton = (ImageView) v.findViewById(R.id.recent_button); + + // Title Card + mTitleCard = (ViewGroup)v.findViewById(R.id.title_card); + mTitleLayout = (ViewGroup) v.findViewById(R.id.title_layout); + mTitle = (TextView) v.findViewById(R.id.title); + mAuthor = (TextView) v.findViewById(R.id.author); + mProgress = (ProgressBar) v.findViewById(R.id.apply_progress); + mOverflow = (ImageView) v.findViewById(R.id.overflow); + mOverflow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isShowingConfirmCancelOverlay()) { + hideConfirmCancelOverlay(); + } else if (isShowingCustomizeResetLayout()) { + hideCustomizeResetLayout(); + } + + PopupMenu popupmenu = new PopupMenu(getActivity(), mTitleCard, Gravity.END); + popupmenu.getMenuInflater().inflate(R.menu.overflow, popupmenu.getMenu()); + + Menu menu = popupmenu.getMenu(); + if (CURRENTLY_APPLIED_THEME.equals(mPkgName) || + mPkgName.equals(Utils.getDefaultThemePackageName(getActivity())) || + mPkgName.equals(ThemeConfig.SYSTEM_DEFAULT)) { + menu.findItem(R.id.menu_delete).setEnabled(false); + } + if (!mThemeTagLayout.isCustomizedTagEnabled()) { + menu.findItem(R.id.menu_reset).setVisible(false); + } + + popupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return onPopupMenuItemClick(item); + } + }); + popupmenu.show(); + } + }); + mCustomize = (ImageView) v.findViewById(R.id.customize); + mCustomize.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (!isShowingConfirmCancelOverlay() && !isShowingCustomizeResetLayout()) { + getChooserActivity().expand(); + } + } + }); + + mDelete = (ImageView) v.findViewById(R.id.delete); + mDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDeleteThemeOverlay(); + } + }); + if (Utils.getDefaultThemePackageName(getActivity()).equals(mPkgName) || + ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { + mDelete.setVisibility(View.GONE); + } + + mReset = (ImageView) v.findViewById(R.id.reset); + mReset.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showResetThemeOverlay(); + } + }); + mReset.setVisibility(View.GONE); + + if (!Utils.hasNavigationBar(getActivity())) { + adjustScrollViewPaddingTop(); + mNavBarCard.setVisibility(View.GONE); + } + + // Additional cards which should hang out offscreen until expanded + mAdditionalCards = (LinearLayout) v.findViewById(R.id.additional_cards); + + mWallpaperCard = (WallpaperCardView) v.findViewById(R.id.wallpaper_card); + mLockScreenCard = (WallpaperCardView) v.findViewById(R.id.lockscreen_card); + int translationY = getDistanceToMoveBelowScreen(mAdditionalCards); + mAdditionalCards.setTranslationY(translationY); + + mConfirmCancelOverlay = (ConfirmCancelOverlay) v.findViewById(R.id.confirm_cancel_overlay); + mClickableView = v.findViewById(R.id.clickable_view); + + mCustomizeResetLayout = v.findViewById(R.id.customize_reset_theme_layout); + mDismissButton = mCustomizeResetLayout.findViewById(R.id.btn_dismiss); + mDismissButton.setOnClickListener(mCustomizeResetClickListener); + mResetButton = mCustomizeResetLayout.findViewById(R.id.btn_reset); + mResetButton.setOnClickListener(mCustomizeResetClickListener); + mCustomizeButton = mCustomizeResetLayout.findViewById(R.id.btn_customize); + mCustomizeButton.setOnClickListener(mCustomizeResetClickListener); + + mProcessingThemeLayout = v.findViewById(R.id.processing_theme_layout); + + if (mPkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { + mThemeTagLayout.setDefaultTagEnabled(true); + } + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + + if (mSkipLoadingAnim) { + mLoadingView.setVisibility(View.GONE); + mTitleLayout.setAlpha(1f); + } + + getLoaderManager().initLoader(LOADER_ID_ALL, null, this); + + setupCardClickListeners(v); + + return v; + } + + @Override + public void onPause() { + super.onPause(); + stopMediaPlayers(); + } + + @Override + public void onResume() { + super.onResume(); + ThemeManager tm = getThemeManager(); + if (tm != null) { + if (isThemeProcessing()) { + tm.registerProcessingListener(this); + mProcessingThemeLayout.setVisibility(View.VISIBLE); + mCustomize.setVisibility(View.INVISIBLE); + mCustomize.setAlpha(0f); + if (mDelete.getVisibility() != View.GONE) { + mDelete.setVisibility(View.INVISIBLE); + mDelete.setAlpha(0f); + } + mProcessingResources = true; + } else { + mCustomize.setVisibility(View.VISIBLE); + mCustomize.setAlpha(1f); + if (mDelete.getVisibility() != View.GONE) { + mDelete.setVisibility(View.VISIBLE); + mDelete.setAlpha(1f); + } + mProcessingResources = false; + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + freeMediaPlayers(); + ThemeManager tm = getThemeManager(); + if (tm != null) { + tm.removeClient(this); + tm.unregisterProcessingListener(this); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == PERMISSION_REQUEST) { + int N = permissions.length; + for (int i = 0; i < N; i++) { + if (READ_EXTERNAL_STORAGE.equals(permissions[i])) { + if (grantResults[i] == PERMISSION_GRANTED) { + // Run the runnable now that we have been granted permission + if (mAfterPermissionGrantedRunnable != null) { + mAfterPermissionGrantedRunnable.run(); + mAfterPermissionGrantedRunnable = null; + } + } else { + // inform the user that they will be unable to pick an image because + // we were not granted permission to do so + Toast.makeText(getActivity(), + R.string.read_external_permission_denied_message, + Toast.LENGTH_LONG).show(); + } + } + } + } + } + + @Override + public void onProgress(int progress) { + mProgress.setProgress(progress); + } + + private void setLiveLockScreenAsKeyguard(boolean setLLS) { + ComponentName cn = null; + if (setLLS) { + try { + final String[] permissions = Utils.getDangerousPermissionsNotGranted(getActivity(), + LLS_PACKAGE_NAME); + if (permissions.length > 0) { + Intent reqIntent = Utils.buildPermissionGrantRequestIntent(getActivity(), + LLS_PACKAGE_NAME, permissions); + if (reqIntent != null) { + startActivity(reqIntent); + } + } + cn = new ComponentName(LLS_PACKAGE_NAME, LLS_PROVIDER_NAME); + } catch (InvalidParameterException e) { + Log.e(TAG, "Package Manager couldn't find package " + LLS_PACKAGE_NAME, e); + return; + } + } + + CmLockPatternUtils lockPatternUtils = new CmLockPatternUtils(getActivity()); + try { + lockPatternUtils.setThirdPartyKeyguard(cn); + } catch (PackageManager.NameNotFoundException e) { + // we should not be here! + } + } + + @Override + public void onFinish(boolean isSuccess) { + // We post a runnable to mHandler so the client is removed from the same thread + mHandler.post(new Runnable() { + @Override + public void run() { + ThemeManager tm = getThemeManager(); + if (tm != null) tm.removeClient(ThemeFragment.this); + } + }); + if (isSuccess) { + if (mExternalLockscreenUri != null) { + // Handle setting an external wallpaper in a separate thread + // Need to do this AFTER ThemeMgr is done processing our change request. + // The external lock screen that we just applied would be removed when + // the change request is setting/clearing the lock screen + new Thread(mApplyExternalLockscreenRunnable).start(); + } + Map appliedComponents = getComponentsToApply(); + boolean modLLS = appliedComponents.containsKey(MODIFIES_LIVE_LOCK_SCREEN); + if (modLLS) { + String pkgName = appliedComponents.get(MODIFIES_LIVE_LOCK_SCREEN); + if (pkgName.equals(LOCKSCREEN_NONE)) { + setLiveLockScreenAsKeyguard(false); + } else { + setLiveLockScreenAsKeyguard(true); + } + } + mProgress.setProgress(100); + animateProgressOut(); + } + getChooserActivity().themeChangeEnd(isSuccess); + } + + @Override + public void onFinishedProcessing(String pkgName) { + if (pkgName.equals(mPkgName) || pkgName.equals(mBaseThemePkgName)) { + ThemeManager tm = getThemeManager(); + if (tm != null) { + tm.unregisterProcessingListener(this); + } + } + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (mThemeTagLayout == null) return; + + if (!isVisibleToUser) { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + } else { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { + PreferenceUtils.removeUpdatedTheme(getActivity(), mPkgName); + } + } + } + + public void setWallpaperImageUri(Uri uri) { + mExternalWallpaperUri = uri; + final Point size = new Point(mWallpaper.getWidth(), mWallpaper.getHeight()); + final Drawable wp = getWallpaperDrawableFromUri(uri, size); + mWallpaperCard.setWallpaper(wp); + mWallpaper.setImageDrawable(wp); + // remove the entry from mSelectedComponentsMap + mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LAUNCHER); + } + + public void setLockscreenImageUri(Uri uri) { + mExternalLockscreenUri = uri; + final Point size = new Point(mLockScreenCard.getWidth(), mLockScreenCard.getHeight()); + final Drawable wp = getWallpaperDrawableFromUri(uri, size); + if (mLockScreenCard.isShowingEmptyView()) { + mLockScreenCard.setEmptyViewEnabled(false); + } + mLockScreenCard.setWallpaper(wp); + // remove the entry from mSelectedComponentsMap + mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); + mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LOCKSCREEN); + } + + protected Drawable getWallpaperDrawableFromUri(Uri uri, Point size) { + final Context context = getActivity(); + final Resources res = context.getResources(); + Bitmap bmp = WallpaperUtils.createPreview(size, context, uri, null, res, 0, 0, false); + if (bmp != null) { + return new BitmapDrawable(res, bmp); + } + return null; + } + + protected ChooserActivity getChooserActivity() { + return (ChooserActivity) getActivity(); + } + + private void adjustScrollViewPaddingTop() { + Resources res = getResources(); + int extraPadding = + res.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; + mScrollView.setPadding(mScrollView.getPaddingLeft(), + mScrollView.getPaddingTop() + extraPadding, mScrollView.getPaddingRight(), + mScrollView.getPaddingBottom()); + } + + protected boolean isThemeProcessing() { + ThemeManager tm = getThemeManager(); + if (tm != null) { + final String pkgName = mBaseThemePkgName != null ? mBaseThemePkgName : mPkgName; + return tm.isThemeBeingProcessed(pkgName); + } + return false; + } + + protected boolean onPopupMenuItemClick(MenuItem item) { + switch(item.getItemId()) { + /* TODO: Add back in once there is UX available for this feature + case R.id.menu_author: + Toast.makeText(getActivity(), + "Not supported", + Toast.LENGTH_LONG).show(); + break; + */ + case R.id.menu_delete: + showDeleteThemeOverlay(); + break; + } + + return true; + } + + public void expand() { + if (mCurrentLoaderId == LOADER_ID_ALL && mCurrentCursor != null) { + loadAdditionalCards(mCurrentCursor); + // we don't need this now that the additional cards are loaded, and + // we don't want to re-load these cards if the we expand again. + mCurrentCursor = null; + } + mClickableView.setVisibility(View.GONE); + mScrollView.setScrollingEnabled(true); + // Full width and height! + ViewGroup content = (ViewGroup) mScrollView.getParent(); + content.setPadding(0, 0, 0, 0); + ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + mPreviewContent.setLayoutParams(layoutParams); + mScrollView.setPadding(0,0,0,0); + + // The parent of the wallpaper squishes the wp slightly because of padding from the 9 patch + // When the parent expands, the wallpaper returns to regular size which creates an + // undesireable effect. + Rect padding = new Rect(); + NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); + bg.getPadding(padding); + mIconCard.setPadding(padding.left, padding.top, padding.right, padding.bottom); + mShadowFrame.setBackground(null); + mShadowFrame.setPadding(0, 0, 0, 0); + + // Off screen cards will become visible and then be animated in + mWallpaperCard.setVisibility(View.VISIBLE); + + // Expand the children + int top = (int) getResources() + .getDimension(R.dimen.expanded_card_margin_top); + for (int i = 0; i < mPreviewContent.getChildCount(); i++) { + ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); + + LinearLayout.LayoutParams lparams = + (LinearLayout.LayoutParams) child.getLayoutParams(); + if (child == mStatusBarCard) { + int statusBarHeight = getResources() + .getDimensionPixelSize(R.dimen.status_bar_height); + lparams.setMargins(0, top + statusBarHeight, 0, 0); + } else { + lparams.setMargins(0, top, 0, 0); + } + + child.setLayoutParams(lparams); + child.expand(false); + } + + // Expand the additional children. + mAdditionalCards.setVisibility(View.VISIBLE); + for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { + View v = mAdditionalCards.getChildAt(i); + if (v instanceof ComponentCardView) { + ComponentCardView card = (ComponentCardView) v; + card.setVisibility(View.VISIBLE); + card.expand(true); + } + } + + // Collect the present position of all the children. The next layout/draw cycle will + // change these bounds since we just expanded them. Then we can animate from prev location + // to the new location. Note that the order of these calls matter as they all + // add themselves to the root layout as overlays + mScrollView.requestLayout(); + animateWallpaperOut(); + animateTitleCard(true, false); + animateChildren(true, getChildrensGlobalBounds(mPreviewContent)); + animateExtras(true); + mSelector = getChooserActivity().getComponentSelector(); + mSelector.setOnItemClickedListener(mOnComponentItemClicked); + if (mBootAnimation != null) mBootAnimation.start(); + hideThemeTagLayout(); + mExpanded = true; + } + + + + // Returns the boundaries for all the children of parent relative to the app window + private List getChildrensGlobalBounds(ViewGroup parent) { + List bounds = new ArrayList(); + for (int i = 0; i < parent.getChildCount(); i++) { + final View v = parent.getChildAt(i); + int[] pos = new int[2]; + v.getLocationInWindow(pos); + Rect boundary = new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1]+v.getHeight()); + bounds.add(boundary); + } + return bounds; + } + + public void performClick(boolean clickedOnContent) { + // Don't do anything if the theme is being processed + if (mProcessingThemeLayout.getVisibility() == View.VISIBLE) return; + + if (clickedOnContent) { + showApplyThemeOverlay(); + } else { + if (isShowingConfirmCancelOverlay()) { + hideConfirmCancelOverlay(); + } + } + } + + public void fadeOutCards(Runnable endAction) { + for (int i = 0; i < mPreviewContent.getChildCount(); i++) { + ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); + v.animateFadeOut(); + } + mHandler.postDelayed(endAction, ComponentCardView.CARD_FADE_DURATION); + } + + public void collapse(final boolean applyTheme) { + mScrollView.setScrollingEnabled(false); + + // Pad the view so it appears thinner + ViewGroup content = (ViewGroup) mScrollView.getParent(); + Resources r = mScrollView.getContext().getResources(); + int leftRightPadding = (int) r.getDimension(R.dimen.collapsed_theme_page_padding); + content.setPadding(leftRightPadding, 0, leftRightPadding, 0); + + if (applyTheme) { + final boolean customized = isThemeCustomized(); + mThemeTagLayout.setCustomizedTagEnabled(customized); + mReset.setVisibility(customized ? View.VISIBLE : View.GONE); + } + + //Move the theme preview so that it is near the center of page per spec + int paddingTop = (int) r.getDimension(R.dimen.collapsed_theme_page_padding_top); + if (!Utils.hasNavigationBar(getActivity())) { + paddingTop += + r.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; + } + mScrollView.setPadding(0, paddingTop, 0, 0); + + // During expand the wallpaper size decreases slightly to makeup for 9patch padding + // so when we collapse we should increase it again. + mShadowFrame.setBackgroundResource(R.drawable.bg_themepreview_shadow); + Rect padding = new Rect(); + final NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); + bg.getPadding(padding); + mShadowFrame.setPadding(padding.left, padding.top, padding.right, padding.bottom); + + // Gradually fade the drop shadow back in or else it will be out of place + ValueAnimator shadowAnimation = ValueAnimator.ofObject(new IntEvaluator(), 0, 255); + shadowAnimation.setDuration(ANIMATE_DURATION); + shadowAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + bg.setAlpha((Integer) animator.getAnimatedValue()); + } + + }); + shadowAnimation.start(); + + //Move the title card back in + mTitleCard.setVisibility(View.VISIBLE); + mTitleCard.setTranslationY(0); + + // Shrink the height + ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); + Resources resources = mPreviewContent.getResources(); + layoutParams.height = (int) resources.getDimension(R.dimen.theme_preview_height); + + mScrollView.requestLayout(); + List bounds = getChildrensGlobalBounds(mPreviewContent); + for (int i = 0; i < mPreviewContent.getChildCount(); i++) { + ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); + LinearLayout.LayoutParams lparams = + (LinearLayout.LayoutParams) child.getLayoutParams(); + lparams.setMargins(0, 0, 0, 0); + + if (child.getId() == R.id.icon_container) { + int top = (int) child.getResources() + .getDimension(R.dimen.collapsed_icon_card_margin_top); + lparams.setMargins(0, top, 0, 0); + } else if (child.getId() == R.id.font_preview_container) { + int top = (int) child.getResources() + .getDimension(R.dimen.collapsed_font_card_margin_top); + lparams.setMargins(0, top, 0, 0); + } else if (child.getId() == R.id.navigation_bar_container) { + int top = (int) child.getResources() + .getDimension(R.dimen.collapsed_navbar_card_margin_top); + lparams.setMargins(0, top, 0, 0); + } + + child.getLayoutParams(); + child.collapse(); + } + + // Collapse additional cards + for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { + View v = mAdditionalCards.getChildAt(i); + if (v instanceof ComponentCardView) { + ComponentCardView card = (ComponentCardView) v; + card.setVisibility(View.VISIBLE); + card.collapse(); + } + } + + animateChildren(false, bounds); + animateExtras(false); + animateWallpaperIn(); + animateTitleCard(false, applyTheme); + if (mBootAnimation != null) mBootAnimation.stop(); + stopMediaPlayers(); + showThemeTagLayout(); + + // Need to set the wallpaper background to black if the user has selected to apply + // the "none" wallpaper + if (applyTheme) { + String pkgName = mSelectedComponentsMap.get(ThemesColumns.MODIFIES_LAUNCHER); + if (pkgName != null && pkgName.length() == 0) { + mWallpaper.setImageResource(R.drawable.wallpaper_none_bg); + } + // we do this here instead of in applyTheme() because this can take a bit longer + // to propagate the change from WallpaperManager back to us + if (mExternalWallpaperUri != null) { + // Handle setting an external wallpaper in a separate thread + new Thread(mApplyExternalWallpaperRunnable).start(); + } + } + mExpanded = false; + } + + // This will animate the children's vertical positions between the previous bounds and the + // new bounds which occur on the next draw + private void animateChildren(final boolean isExpanding, final List prevBounds) { + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + final Resources res = getResources(); + final float yOffset = + res.getDimensionPixelSize(R.dimen.expand_collapse_child_offset) + * (isExpanding ? -1 : 1); + // Grab the child's new location and animate from prev to current loc. + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + for (int i = mPreviewContent.getChildCount() - 1; i >= 0; i--) { + final ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); + + float prevY; + float endY; + float prevHeight; + float endHeight; + if (i >= prevBounds.size()) { + // View is being created + prevY = mPreviewContent.getTop() + mPreviewContent.getHeight(); + endY = v.getY(); + prevHeight = v.getHeight(); + endHeight = v.getHeight(); + } else { + Rect boundary = prevBounds.get(i); + prevY = boundary.top; + prevHeight = boundary.height(); + + int[] endPos = new int[2]; + v.getLocationInWindow(endPos); + endY = endPos[1]; + endHeight = v.getHeight(); + } + + int paddingTop = v.getPaddingTop() / 2; + float dy = (prevY - endY - paddingTop) + (prevHeight - endHeight) / 2; + dy += yOffset; + v.setTranslationY(dy); + root.getOverlay().add(v); + + // Expanding has a delay while the wallpaper begins to fade out + // Collapsing is opposite of this so wallpaper will have the delay instead + int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; + + v.animate() + .setStartDelay(startDelay) + .translationY(0) + .setDuration(ANIMATE_DURATION) + .setInterpolator( + new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(v); + mPreviewContent.addView(v, 0); + } + }); + v.postDelayed(new Runnable() { + public void run() { + if (isExpanding) { + v.animateExpand(); + } + } + }, ANIMATE_DURATION / 2); + } + return true; + } + }); + } + + private void animateExtras(final boolean isExpanding) { + int[] pos = new int[2]; + mAdditionalCards.getLocationInWindow(pos); + final ViewGroup parent = (ViewGroup) mAdditionalCards.getParent(); + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + // During a collapse we don't want the card to shrink so add it to the overlay now + // During an expand we want the card to expand so add it to the overlay post-layout + if (!isExpanding) { + root.getOverlay().add(mAdditionalCards); + } + + // Expanding has a delay while the wallpaper begins to fade out + // Collapsing is opposite of this so wallpaper will have the delay instead + final int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + int translationY = 0; + if (isExpanding) { + root.getOverlay().add(mAdditionalCards); + } else { + translationY = getDistanceToMoveBelowScreen(mAdditionalCards); + } + + int duration = isExpanding ? ANIMATE_DURATION + 100 : ANIMATE_DURATION; + mAdditionalCards.animate() + .setStartDelay(startDelay) + .translationY(translationY) + .setDuration(duration) + .setInterpolator( + new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) + .withEndAction(new Runnable() { + public void run() { + if (!isExpanding) { + mAdditionalCards.setVisibility(View.INVISIBLE); + } + root.getOverlay().remove(mAdditionalCards); + parent.addView(mAdditionalCards); + } + }); + return false; + } + }); + } + + private int getDistanceToMoveBelowScreen(View v) { + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point p = new Point(); + display.getSize(p); + int heightId = getResources() + .getIdentifier("navigation_bar_height", "dimen", "android"); + int navbar_height = getResources().getDimensionPixelSize(heightId); + int[] pos = new int[2]; + v.getLocationInWindow(pos); + return p.y + navbar_height - pos[1]; + } + + private void animateTitleCard(final boolean expand, final boolean applyTheme) { + final ViewGroup parent = (ViewGroup) mTitleCard.getParent(); + // Get current location of the title card + int[] location = new int[2]; + mTitleCard.getLocationOnScreen(location); + final int prevY = location[1]; + final int position = parent.indexOfChild(mTitleCard); + + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + root.getOverlay().add(mTitleCard); + + //Move title card back where it was before the relayout + float alpha = 1f; + if (expand) { + int[] endPos = new int[2]; + mTitleCard.getLocationInWindow(endPos); + int endY = endPos[1]; + mTitleCard.setTranslationY(prevY - endY); + alpha = 0; + } else { + } + + // Fade the title card and move it out of the way + mTitleCard.animate() + .alpha(alpha) + .setDuration(ANIMATE_DURATION) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(mTitleCard); + parent.addView(mTitleCard, position); + if (expand) { + mTitleCard.setVisibility(View.INVISIBLE); + } else { + mTitleCard.setVisibility(View.VISIBLE); + mClickableView.setVisibility(View.VISIBLE); + if (applyTheme) { + // The title card is the last animation when collapsing so + // we will handle applying the theme, if applicable, here + applyTheme(); + } + } + } + }); + return true; + } + }); + } + + private void animateWallpaperOut() { + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + int[] location = new int[2]; + mWallpaper.getLocationOnScreen(location); + + final int prevY = location[1]; + + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + root.getOverlay().add(mWallpaper); + + int[] location = new int[2]; + mWallpaper.getLocationOnScreen(location); + final int newY = location[1]; + + mWallpaper.setTranslationY(prevY - newY); + mWallpaper.animate() + .alpha(0f) + .setDuration(300) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(mWallpaper); + mShadowFrame.addView(mWallpaper, 0); + mWallpaper.setVisibility(View.GONE); + } + }); + return true; + + } + }); + } + + private void animateWallpaperIn() { + mWallpaper.setVisibility(View.VISIBLE); + mWallpaper.setTranslationY(0); + mWallpaper.animate() + .alpha(1f) + .setDuration(300); + } + + protected String getAppliedFontPackageName() { + final Configuration config = getActivity().getResources().getConfiguration(); + final ThemeConfig themeConfig = config != null ? config.themeConfig : null; + return themeConfig != null ? themeConfig.getFontPkgName() : + ThemeConfig.getSystemTheme().getFontPkgName(); + } + + protected ThemeManager getThemeManager() { + return ThemeManager.getInstance(getActivity()); + } + + private void freeMediaPlayers() { + for (MediaPlayer mp : mMediaPlayers.values()) { + if (mp != null) { + mp.stop(); + mp.release(); + } + } + mMediaPlayers.clear(); + } + + protected View.OnClickListener mPlayPauseClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaPlayer mp = (MediaPlayer) v.getTag(); + if (mp != null) { + if (mp.isPlaying()) { + ((ImageView) v).setImageResource(R.drawable.media_sound_preview); + mp.pause(); + mp.seekTo(0); + } else { + stopMediaPlayers(); + ((ImageView) v).setImageResource(R.drawable.media_sound_stop); + mp.start(); + } + } + } + }; + + protected MediaPlayer.OnCompletionListener mPlayCompletionListener + = new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + for (ImageView v : mMediaPlayers.keySet()) { + if (mp == mMediaPlayers.get(v)) { + if (v != null) { + v.setImageResource(R.drawable.media_sound_preview); + } + } + } + } + }; + + private void stopMediaPlayers() { + for (ImageView v : mMediaPlayers.keySet()) { + if (v != null) { + v.setImageResource(R.drawable.media_sound_preview); + } + MediaPlayer mp = mMediaPlayers.get(v); + if (mp != null && mp.isPlaying()) { + mp.pause(); + mp.seekTo(0); + } + } + } + + protected void resetTheme() { + mSelectedComponentsMap.clear(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, mBaseThemePkgName); + args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); + getLoaderManager().restartLoader(LOADER_ID_ALL, args, this); + mThemeResetting = true; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + String pkgName = mPkgName; + long componentId = DEFAULT_COMPONENT_ID; + if (args != null) { + pkgName = args.getString(ARG_PACKAGE_NAME); + componentId = args.getLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); + } + return CursorLoaderHelper.themeFragmentCursorLoader(getActivity(), id, pkgName, + componentId); + } + + @Override + public void onLoadFinished(Loader loader, Cursor c) { + if (c.getCount() == 0) return; + mCurrentCursor = c; + mCurrentLoaderId = loader.getId(); + c.moveToFirst(); + boolean animate = !mApplyThemeOnPopulated; + switch (mCurrentLoaderId) { + case LOADER_ID_ALL: + if (mProcessingResources && !isThemeProcessing()) { + mProcessingResources = false; + hideProcessingOverlay(); + } + loadLegacyThemeInfo(c); + populateSupportedComponents(c); + loadWallpaper(c, false); + loadStatusBar(c, false); + loadIcons(c, false); + loadNavBar(c, false); + loadTitle(c); + loadFont(c, false); + mHandler.post(new Runnable() { + @Override + public void run() { + animateContentIn(); + } + }); + if (mShowLockScreenSelectorAfterContentLoaded) { + getChooserActivity().expandContentAndAnimateLockScreenCardIn(); + mShowLockScreenSelectorAfterContentLoaded = false; + } + break; + case LOADER_ID_STATUS_BAR: + loadStatusBar(c, animate); + break; + case LOADER_ID_FONT: + loadFont(c, animate); + break; + case LOADER_ID_ICONS: + loadIcons(c, animate); + break; + case LOADER_ID_WALLPAPER: + loadWallpaper(c, animate); + break; + case LOADER_ID_NAVIGATION_BAR: + loadNavBar(c, animate); + break; + case LOADER_ID_LIVE_LOCK_SCREEN: + case LOADER_ID_LOCKSCREEN: + loadLockScreen(c, animate); + break; + case LOADER_ID_STYLE: + loadStyle(c, animate); + break; + case LOADER_ID_BOOT_ANIMATION: + loadBootAnimation(c); + break; + case LOADER_ID_RINGTONE: + loadAudible(RingtoneManager.TYPE_RINGTONE, c, animate); + break; + case LOADER_ID_NOTIFICATION: + loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, animate); + break; + case LOADER_ID_ALARM: + loadAudible(RingtoneManager.TYPE_ALARM, c, animate); + break; + } + if (mCurrentLoaderId != LOADER_ID_ALL) { + if (!componentsChanged()) { + getChooserActivity().hideSaveApplyButton(); + } else if (!mApplyThemeOnPopulated) { + getChooserActivity().showSaveApplyButton(); + } + } + } + + @Override + public void onLoaderReset(Loader loader) {} + + private void loadAdditionalCards(Cursor c) { + for(int i=0; i < mAdditionalCards.getChildCount(); i++) { + View v = mAdditionalCards.getChildAt(i); + if (v instanceof ComponentCardView) { + String component = mCardIdsToComponentTypes.get(v.getId()); + loadAdditionalCard(c, component, shouldShowComponentCard(component)); + } + } + } + + private void loadAdditionalCard(Cursor c, String component, boolean hasContent) { + if (MODIFIES_LOCKSCREEN.equals(component)) { + if (hasContent) { + loadLockScreen(c, false); + } else { + mLockScreenCard.clearWallpaper(); + mLockScreenCard.setEmptyViewEnabled(true); + setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); + } + } else if (MODIFIES_LAUNCHER.equals(component)) { + // this was already loaded so no need to do this again. + } else if (MODIFIES_OVERLAYS.equals(component)) { + if (hasContent) { + loadStyle(c, false); + } else { + mStyleCard.setEmptyViewEnabled(true); + setAddComponentTitle(mStyleCard, + getString(R.string.style_label)); + } + } else if (MODIFIES_BOOT_ANIM.equals(component)) { + if (hasContent) { + loadBootAnimation(c); + } else { + mBootAnimationCard.setEmptyViewEnabled(true); + setAddComponentTitle(mBootAnimationCard, + getString(R.string.boot_animation_label)); + } + } else if (MODIFIES_RINGTONES.equals(component)) { + if (hasContent) { + loadAudible(RingtoneManager.TYPE_RINGTONE, c, false); + } else { + mRingtoneCard.setEmptyViewEnabled(true); + setAddComponentTitle(mRingtoneCard, + getAudibleLabel(RingtoneManager.TYPE_RINGTONE)); + } + } else if (MODIFIES_NOTIFICATIONS.equals(component)) { + if (hasContent) { + loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, false); + } else { + mNotificationCard.setEmptyViewEnabled(true); + setAddComponentTitle(mNotificationCard, + getAudibleLabel(RingtoneManager.TYPE_NOTIFICATION)); + } + } else if (MODIFIES_ALARMS.equals(component)) { + if (hasContent) { + loadAudible(RingtoneManager.TYPE_ALARM, c, false); + } else { + mAlarmCard.setEmptyViewEnabled(true); + setAddComponentTitle(mAlarmCard, + getAudibleLabel(RingtoneManager.TYPE_ALARM)); + } + } else { + throw new IllegalArgumentException("Don't know how to load: " + component); + } + } + + protected void populateSupportedComponents(Cursor c) { + List components = ThemeUtils.getAllComponents(); + for(String component : components) { + int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int modifiesCompIdx = c.getColumnIndex(component); + + String pkg = c.getString(pkgIdx); + boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); + if (supported) { + mBaseThemeSupportedComponents.add(component); + mSelectedComponentsMap.put(component, pkg); + } + } + + if (mApplyThemeOnPopulated) { + applyTheme(); + } + } + + /** + * Determines whether a card should be shown or not. + * UX Rules: + * 1) "My Theme" always shows all cards + * 2) Other themes only show what has been implemented in the theme + * + */ + protected Boolean shouldShowComponentCard(String component) { + String pkg = mSelectedComponentsMap.get(component); + return pkg != null && pkg.equals(mPkgName); + } + + protected void loadLegacyThemeInfo(Cursor c) { + int targetApiIdx = c.getColumnIndex(ThemesColumns.TARGET_API); + // If this is being called for a MyThemeFragment the index will be -1 so set to + // SYSTEM_TARGET_API so we don't display the tag. If the user applied a legacy theme + // then they should have already been warned. + int targetApi = targetApiIdx < 0 ? SYSTEM_TARGET_API : c.getInt(targetApiIdx); + mIsLegacyTheme = targetApi != SYSTEM_TARGET_API && targetApi <= Build.VERSION_CODES.KITKAT; + mThemeTagLayout.setLegacyTagEnabled(mIsLegacyTheme); + } + + protected void loadTitle(Cursor c) { + int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); + int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); + mTitle.setText(c.getString(titleIdx)); + mAuthor.setText(c.getString(authorIdx)); + } + + protected void loadWallpaper(Cursor c, boolean animate) { + mExternalWallpaperUri = null; + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mWallpaperCard, true); + } + if (mWallpaperCard.isShowingEmptyView()) mWallpaperCard.setEmptyViewEnabled(false); + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); + int cmpntIdIdx = c.getColumnIndex(PreviewColumns.COMPONENT_ID); + final Resources res = getResources(); + Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); + if (bitmap != null) { + mWallpaper.setImageBitmap(bitmap); + mWallpaperCard.setWallpaper(new BitmapDrawable(res, bitmap)); + String pkgName = c.getString(pkgNameIdx); + Long cmpntId = (cmpntIdIdx >= 0) ? c.getLong(cmpntIdIdx) : DEFAULT_COMPONENT_ID; + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_LAUNCHER))) { + mSelectedComponentsMap.put(MODIFIES_LAUNCHER, pkgName); + mSelectedWallpaperComponentId = cmpntId; + setCardTitle(mWallpaperCard, pkgName, getString(R.string.wallpaper_label)); + } + } else { + // Set the wallpaper to "None" + mWallpaperCard.setWallpaper(null); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, getString(R.string.wallpaper_label)); + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); + } + + if (animate) { + animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); + } + } + + protected void loadLockScreen(Cursor c, boolean animate) { + mExternalLockscreenUri = null; + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mLockScreenCard, true); + } + if (mLockScreenCard.isShowingEmptyView()) mLockScreenCard.setEmptyViewEnabled(false); + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int liveLockIndex = c.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); + boolean isLiveLockScreen = liveLockIndex >= 0 && c.getInt(liveLockIndex) == 1; + + int wpIdx = isLiveLockScreen + ? c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW) + : c.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_PREVIEW); + final Resources res = getResources(); + Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); + if (bitmap != null) { + mLockScreenCard.setWallpaper(new BitmapDrawable(res, bitmap)); + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && (mBaseThemeSupportedComponents.contains(MODIFIES_LOCKSCREEN) || + mBaseThemeSupportedComponents.contains(MODIFIES_LIVE_LOCK_SCREEN)))) { + if (isLiveLockScreen) { + mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, pkgName); + if (mCurrentTheme.containsKey(MODIFIES_LOCKSCREEN)) { + mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); + } else { + mSelectedComponentsMap.remove(MODIFIES_LOCKSCREEN); + } + setCardTitle(mLockScreenCard, pkgName, + getString(R.string.live_lock_screen_label)); + } else { + mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, pkgName); + if (mCurrentTheme.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { + mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); + } else { + mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); + } + setCardTitle(mLockScreenCard, pkgName, getString(R.string.lockscreen_label)); + } + } + } else { + // Set the lockscreen wallpaper to "None" + mLockScreenCard.setWallpaper(null); + setCardTitle(mLockScreenCard, WALLPAPER_NONE, getString(R.string.lockscreen_label)); + } + + if (animate) { + animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); + } + } + + protected void loadStatusBar(Cursor c, boolean animate) { + int backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + int wifiIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); + int wifiMarginIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END); + int bluetoothIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); + int signalIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); + int batteryIdx = c.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); + int clockColorIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR); + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + + Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); + Bitmap bluetoothIcon = Utils.loadBitmapBlob(c, bluetoothIdx); + Bitmap wifiIcon = Utils.loadBitmapBlob(c, wifiIdx); + Bitmap signalIcon = Utils.loadBitmapBlob(c, signalIdx); + Bitmap batteryIcon = Utils.loadBitmapBlob(c, batteryIdx); + int wifiMargin = wifiMarginIdx != -1 ? c.getInt(wifiMarginIdx) : DEFAULT_WIFI_MARGIN; + int clockTextColor = clockColorIdx != -1 ? c.getInt(clockColorIdx) : DEFAULT_CLOCK_COLOR; + + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mStatusBar, false); + } + if (mStatusBarCard.isShowingEmptyView()) mStatusBarCard.setEmptyViewEnabled(false); + + mStatusBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); + mBluetooth.setImageBitmap(bluetoothIcon); + mWifi.setImageBitmap(wifiIcon); + mSignal.setImageBitmap(signalIcon); + mBattery.setImageBitmap(batteryIcon); + mClock.setTextColor(clockTextColor); + + ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) mWifi.getLayoutParams(); + params.setMarginEnd(wifiMargin); + mWifi.setLayoutParams(params); + + if (mBatteryStyle == 4) { + mBattery.setVisibility(View.GONE); + } else { + mBattery.setVisibility(View.VISIBLE); + } + mStatusBar.post(new Runnable() { + @Override + public void run() { + mStatusBar.invalidate(); + } + }); + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_STATUS_BAR))) { + mSelectedComponentsMap.put(MODIFIES_STATUS_BAR, pkgName); + setCardTitle(mStatusBarCard, pkgName, + getString(R.string.statusbar_label)); + } + } + if (animate) { + animateContentChange(R.id.status_bar_container, mStatusBar, overlay); + } + } + + protected void loadIcons(Cursor c, boolean animate) { + if (mIconCard.isShowingEmptyView()) { + mIconCard.setEmptyViewEnabled(false); + } + int[] iconIdx = new int[3]; + iconIdx[0] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); + iconIdx[1] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_2); + iconIdx[2] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_3); + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + + // Set the icons. If the provider does not have an icon preview then + // fall back to the default icon set + IconPreviewHelper helper = new IconPreviewHelper(getActivity(), ""); + ViewGroup iconContainer = + (ViewGroup) mIconCard.findViewById(R.id.icon_preview_container); + int numOfChildren = iconContainer.getChildCount(); + + List iconViews = new ArrayList(numOfChildren); + for(int i=0; i < numOfChildren; i++) { + final View view = iconContainer.getChildAt(i); + if (!(view instanceof ImageView)) continue; + iconViews.add((ImageView) view); + } + + for(int i=0; i < iconViews.size() && i < iconIdx.length; i++) { + final ImageView v = iconViews.get(i); + Bitmap bitmap = Utils.loadBitmapBlob(c, iconIdx[i]); + Drawable oldIcon = v.getDrawable(); + Drawable newIcon; + if (bitmap == null) { + ComponentName component = sIconComponents[i]; + newIcon = helper.getDefaultIcon(component.getPackageName(), + component.getClassName()); + } else { + newIcon = new BitmapDrawable(getResources(), bitmap); + } + if (animate) { + Drawable[] layers = new Drawable[2]; + layers[0] = oldIcon instanceof IconTransitionDrawable ? + ((IconTransitionDrawable) oldIcon).getDrawable(1) : oldIcon; + layers[1] = newIcon; + final IconTransitionDrawable itd = new IconTransitionDrawable(layers); + v.postDelayed(new Runnable() { + @Override + public void run() { + itd.startTransition(ANIMATE_COMPONENT_CHANGE_DURATION); + v.setImageDrawable(itd); + } + }, ANIMATE_COMPONENT_ICON_DELAY * i); + } else { + v.setImageDrawable(newIcon); + } + } + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_ICONS))) { + mSelectedComponentsMap.put(MODIFIES_ICONS, pkgName); + setCardTitle(mIconCard, pkgName, + getString(R.string.icon_label)); + } + } + } + + protected void loadNavBar(Cursor c, boolean animate) { + int backButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); + int homeButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_HOME_BUTTON); + int recentButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_RECENT_BUTTON); + int backgroundIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACKGROUND); + if (backgroundIdx == -1) { + backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + } + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + + Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); + Bitmap backButton = Utils.loadBitmapBlob(c, backButtonIdx); + Bitmap homeButton = Utils.loadBitmapBlob(c, homeButtonIdx); + Bitmap recentButton = Utils.loadBitmapBlob(c, recentButtonIdx); + + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mNavBar, false); + } + if (mNavBarCard.isShowingEmptyView()) mNavBarCard.setEmptyViewEnabled(false); + + mNavBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); + mBackButton.setImageBitmap(backButton); + mHomeButton.setImageBitmap(homeButton); + mRecentButton.setImageBitmap(recentButton); + + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_NAVIGATION_BAR))) { + mSelectedComponentsMap.put(MODIFIES_NAVIGATION_BAR, pkgName); + setCardTitle(mNavBarCard, pkgName, getString(R.string.navbar_label)); + } + } + if (animate) { + animateContentChange(R.id.navigation_bar_container, mNavBar, overlay); + } + } + + protected void loadFont(Cursor c, boolean animate) { + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mFontPreview, true); + } + if (mFontCard.isShowingEmptyView()) mFontCard.setEmptyViewEnabled(false); + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + String pkgName = pkgNameIdx >= 0 ? c.getString(pkgNameIdx) : mPkgName; + TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); + ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), pkgName); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + mFontPreview.setTypeface(mTypefaceNormal); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_FONTS))) { + mSelectedComponentsMap.put(MODIFIES_FONTS, pkgName); + setCardTitle(mFontCard, pkgName, getString(R.string.font_label)); + } + + if (animate) { + animateContentChange(R.id.font_preview_container, mFontPreview, overlay); + } + } + + protected void loadStyle(Cursor c, boolean animate) { + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mStylePreview, true); + } + if (mStyleCard.isShowingEmptyView()) { + mStyleCard.setEmptyViewEnabled(false); + } + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int styleIdx = c.getColumnIndex(PreviewColumns.STYLE_PREVIEW); + mStylePreview.setImageBitmap(Utils.loadBitmapBlob(c, styleIdx)); + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_OVERLAYS))) { + mSelectedComponentsMap.put(MODIFIES_OVERLAYS, pkgName); + setCardTitle(mStyleCard, pkgName, + getString(R.string.style_label)); + } + } + if (animate) { + animateContentChange(R.id.style_card, mStylePreview, overlay); + } + } + + protected void loadBootAnimation(Cursor c) { + if (mBootAnimationCard.isShowingEmptyView()) { + mBootAnimationCard.setEmptyViewEnabled(false); + } + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + if (mBootAnimation != null) { + String pkgName; + if (pkgNameIdx > -1) { + pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_BOOT_ANIM))) { + mSelectedComponentsMap.put(MODIFIES_BOOT_ANIM, pkgName); + setCardTitle(mBootAnimationCard, pkgName, + getString(R.string.boot_animation_label)); + } + } else { + pkgName = mCurrentTheme.get(MODIFIES_BOOT_ANIM); + } + mBootAnimation.stop(); + new AnimationLoader(getActivity(), pkgName, mBootAnimation).execute(); + } + } + + protected void loadAudible(int type, Cursor c, boolean animate) { + ComponentCardView audibleContainer = null; + ImageView playPause = null; + String component = null; + int parentResId = 0; + switch (type) { + case RingtoneManager.TYPE_RINGTONE: + audibleContainer = mRingtoneCard; + playPause = mRingtonePlayPause; + component = MODIFIES_RINGTONES; + parentResId = R.id.ringtone_preview_container; + break; + case RingtoneManager.TYPE_NOTIFICATION: + audibleContainer = mNotificationCard; + playPause = mNotificationPlayPause; + component = MODIFIES_NOTIFICATIONS; + parentResId = R.id.notification_preview_container; + break; + case RingtoneManager.TYPE_ALARM: + audibleContainer = mAlarmCard; + playPause = mAlarmPlayPause; + component = MODIFIES_ALARMS; + parentResId = R.id.alarm_preview_container; + break; + } + if (audibleContainer == null) return; + + View content = audibleContainer.findViewById(R.id.content); + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(content, true); + } + if (audibleContainer.isShowingEmptyView()) { + audibleContainer.setEmptyViewEnabled(false); + } + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); + if (playPause == null) { + playPause = (ImageView) audibleContainer.findViewById(R.id.play_pause); + } + TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); + MediaPlayer mp = mMediaPlayers.get(playPause); + if (mp == null) { + mp = new MediaPlayer(); + } + String pkgName = c.getString(pkgNameIdx); + setCardTitle(audibleContainer, pkgName, getAudibleLabel(type)); + AudibleLoadingThread thread = new AudibleLoadingThread(getActivity(), type, pkgName, mp); + title.setText(c.getString(titleIdx)); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(component))) { + mSelectedComponentsMap.put(component, pkgName); + } + + playPause.setVisibility(View.VISIBLE); + playPause.setTag(mp); + mMediaPlayers.put(playPause, mp); + playPause.setOnClickListener(mPlayPauseClickListener); + mp.setOnCompletionListener(mPlayCompletionListener); + if (animate) { + animateContentChange(parentResId, content, overlay); + } + thread.start(); + } + + protected Drawable getOverlayDrawable(View v, boolean requiresTransparency) { + if (!v.isDrawingCacheEnabled()) v.setDrawingCacheEnabled(true); + Bitmap cache = v.getDrawingCache(true).copy( + requiresTransparency ? Config.ARGB_8888 : Config.RGB_565, false); + Drawable d = cache != null ? new BitmapDrawable(getResources(), cache) : null; + v.destroyDrawingCache(); + + return d; + } + + protected String getAudibleLabel(int type) { + switch (type) { + case RingtoneManager.TYPE_RINGTONE: + return getString(R.string.ringtone_label); + case RingtoneManager.TYPE_NOTIFICATION: + return getString(R.string.notification_label); + case RingtoneManager.TYPE_ALARM: + return getString(R.string.alarm_label); + } + return null; + } + + protected void setCardTitle(ComponentCardView card, String pkgName, String title) { + TextView tv = (TextView) card.findViewById(R.id.label); + if (Utils.getDefaultThemePackageName(getActivity()).equals(pkgName)) { + tv.setText(getString(R.string.default_tag_text) + " " + title); + } else { + tv.setText(title); + } + } + + protected void setAddComponentTitle(ComponentCardView card, String title) { + TextView tv = (TextView) card.findViewById(R.id.label); + tv.setText(getString(R.string.add_component_text) + " " + title); + } + + public static ComponentName[] getIconComponents(Context context) { + if (sIconComponents == null || sIconComponents.length == 0) { + sIconComponents = new ComponentName[]{COMPONENT_DIALER, COMPONENT_MESSAGING, + COMPONENT_CAMERA, COMPONENT_BROWSER}; + + PackageManager pm = context.getPackageManager(); + + // if device does not have telephony replace dialer and mms + if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + sIconComponents[0] = COMPONENT_CALENDAR; + sIconComponents[1] = COMPONENT_GALERY; + } else { + // decide on which dialer icon to use + try { + if (pm.getPackageInfo(DIALER_NEXT_PACKAGE, 0) != null) { + sIconComponents[0] = COMPONENT_DIALERNEXT; + } + } catch (PackageManager.NameNotFoundException e) { + // default to COMPONENT_DIALER + } + } + + if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) { + sIconComponents[2] = COMPONENT_SETTINGS; + } else { + // decide on which camera icon to use + try { + if (pm.getPackageInfo(CAMERA_NEXT_PACKAGE, 0) != null) { + sIconComponents[2] = COMPONENT_CAMERANEXT; + } + } catch (PackageManager.NameNotFoundException e) { + // default to COMPONENT_CAMERA + } + } + + } + return sIconComponents; + } + + private void setupCardClickListeners(View parent) { + for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { + parent.findViewById(mCardIdsToComponentTypes.keyAt(i)) + .setOnClickListener(mCardClickListener); + } + } + + private View.OnClickListener mCardClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isShowingConfirmCancelOverlay() || isShowingCustomizeResetLayout()) return; + if (mActiveCardId > 0) { + // need to fade the newly selected card in if another was currently selected. + ((ComponentCardView) v).animateCardFadeIn(); + } + mActiveCardId = v.getId(); + String component = mCardIdsToComponentTypes.get(mActiveCardId); + // Only pass on mSelectedWallpaperComponentId if dealing with mods_launcher + long selectedComponentId = (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) ? + mSelectedWallpaperComponentId : DEFAULT_COMPONENT_ID; + String pkgName = mSelectedComponentsMap.get(component); + if (component.equals(MODIFIES_LOCKSCREEN) && TextUtils.isEmpty(pkgName)) { + String liveLockScreenPkg = mSelectedComponentsMap.get(MODIFIES_LIVE_LOCK_SCREEN); + if (liveLockScreenPkg != null) { + pkgName = liveLockScreenPkg; + } + } + getChooserActivity().showComponentSelector(component, pkgName, selectedComponentId, v); + fadeOutNonSelectedCards(mActiveCardId); + stopMediaPlayers(); + } + }; + + private ConfirmCancelOverlay.OnOverlayDismissedListener mApplyCancelListener = + new ConfirmCancelOverlay.OnOverlayDismissedListener() { + @Override + public void onDismissed(boolean accepted) { + hideConfirmCancelOverlay(accepted); + } + }; + + private ConfirmCancelOverlay.OnOverlayDismissedListener mDeleteConfirmationListener = + new ConfirmCancelOverlay.OnOverlayDismissedListener() { + @Override + public void onDismissed(boolean accepted) { + if (accepted) uninstallTheme(); + hideConfirmCancelOverlay(); + } + }; + + private ConfirmCancelOverlay.OnOverlayDismissedListener mResetConfirmationListener = + new ConfirmCancelOverlay.OnOverlayDismissedListener() { + @Override + public void onDismissed(boolean accepted) { + if (accepted) resetTheme(); + hideConfirmCancelOverlay(); + } + }; + + private View.OnClickListener mCustomizeResetClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v == mDismissButton) { + hideCustomizeResetLayout(CustomizeResetAction.Dismiss); + } else if (v == mResetButton) { + hideCustomizeResetLayout(CustomizeResetAction.Reset); + } else if (v == mCustomizeButton) { + hideCustomizeResetLayout(CustomizeResetAction.Customize); + } + } + }; + + protected void loadComponentFromPackage(String pkgName, String component, long componentId) { + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, pkgName); + args.putLong(ARG_COMPONENT_ID, componentId); + int loaderId = LOADER_ID_INVALID; + if (MODIFIES_STATUS_BAR.equals(component)) { + loaderId = LOADER_ID_STATUS_BAR; + } else if (MODIFIES_FONTS.equals(component)) { + loaderId = LOADER_ID_FONT; + } else if (MODIFIES_ICONS.equals(component)) { + loaderId = LOADER_ID_ICONS; + } else if (MODIFIES_NAVIGATION_BAR.equals(component)) { + loaderId = LOADER_ID_NAVIGATION_BAR; + } else if (MODIFIES_LAUNCHER.equals(component)) { + if (pkgName != null) { + if (TextUtils.isEmpty(pkgName)) { + mWallpaperCard.setWallpaper(null); + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, + getString(R.string.wallpaper_label)); + getChooserActivity().showSaveApplyButton(); + } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { + // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, + // otherwise let the user pick an image + if (getActivity().checkSelfPermission( + READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { + mAfterPermissionGrantedRunnable = new Runnable() { + @Override + public void run() { + getChooserActivity().pickExternalWallpaper(); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, + getString(R.string.wallpaper_label)); + } + }; + requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, + PERMISSION_REQUEST); + } else { + getChooserActivity().pickExternalWallpaper(); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, + getString(R.string.wallpaper_label)); + } + } else { + loaderId = LOADER_ID_WALLPAPER; + } + } + } else if (MODIFIES_LOCKSCREEN.equals(component) + || MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { + if (pkgName != null && TextUtils.isEmpty(pkgName)) { + mLockScreenCard.setWallpaper(null); + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); + + if(mSelectedComponentsMap.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { + mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); + } + setCardTitle(mLockScreenCard, WALLPAPER_NONE, + getString(R.string.lockscreen_label)); + if (mLockScreenCard.isShowingEmptyView()) { + mLockScreenCard.setEmptyViewEnabled(false); + } + getChooserActivity().showSaveApplyButton(); + } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { + // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, + // otherwise let the user pick an image + if (getActivity().checkSelfPermission( + READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { + mAfterPermissionGrantedRunnable = new Runnable() { + @Override + public void run() { + getChooserActivity().pickExternalLockscreen(); + setCardTitle(mLockScreenCard, WALLPAPER_NONE, + getString(R.string.lockscreen_label)); + } + }; + requestPermissions(new String[] {READ_EXTERNAL_STORAGE}, + PERMISSION_REQUEST); + } else { + getChooserActivity().pickExternalLockscreen(); + setCardTitle(mLockScreenCard, WALLPAPER_NONE, + getString(R.string.lockscreen_label)); + } + } else if (ComponentSelector.MOD_LOCK.equals(pkgName)) { + startLiveLockScreenSettings(); + } else { + if (MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { + loaderId = LOADER_ID_LIVE_LOCK_SCREEN; + } else { + loaderId = LOADER_ID_LOCKSCREEN; + } + } + } else if (MODIFIES_OVERLAYS.equals(component)) { + loaderId = LOADER_ID_STYLE; + } else if (MODIFIES_BOOT_ANIM.equals(component)) { + loaderId = LOADER_ID_BOOT_ANIMATION; + } else if (MODIFIES_RINGTONES.equals(component)) { + loaderId = LOADER_ID_RINGTONE; + } else if (MODIFIES_NOTIFICATIONS.equals(component)) { + loaderId = LOADER_ID_NOTIFICATION; + } else if (MODIFIES_ALARMS.equals(component)) { + loaderId = LOADER_ID_ALARM; + } else { + return; + } + + if (loaderId != LOADER_ID_INVALID) { + getLoaderManager().restartLoader(loaderId, args, ThemeFragment.this); + } + } + + private OnItemClickedListener mOnComponentItemClicked = new OnItemClickedListener() { + @Override + public void onItemClicked(String pkgName, long componentId, Bundle params) { + String component = mSelector.getComponentType(); + if (MODIFIES_LOCKSCREEN.equals(component) && params != null) { + boolean isLiveLockView = params.getBoolean( + ComponentSelector.IS_LIVE_LOCK_SCREEN_VIEW,false); + if (isLiveLockView) { + //We got here because an live lock thubmnail view was clicked. We need to + //replace the component to load the proper data from the provider. + component = MODIFIES_LIVE_LOCK_SCREEN; + } + } + loadComponentFromPackage(pkgName, component, componentId); + } + }; + + private void fadeOutNonSelectedCards(int selectedCardId) { + for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { + if (mCardIdsToComponentTypes.keyAt(i) != selectedCardId) { + ComponentCardView card = (ComponentCardView) getView().findViewById( + mCardIdsToComponentTypes.keyAt(i)); + if (card != null) card.animateCardFadeOut(); + } + } + } + + protected void animateContentChange(int parentId, View viewToAnimate, Drawable overlay) { + ((ComponentCardView) getView().findViewById(parentId)) + .animateContentChange(viewToAnimate, overlay, ANIMATE_COMPONENT_CHANGE_DURATION); + } + + private Runnable mApplyThemeRunnable = new Runnable() { + @Override + public void run() { + final Context context = getActivity(); + if (context != null) { + // Post this on mHandler so the client is added and removed from the same + // thread + mHandler.post(new Runnable() { + @Override + public void run() { + final Map componentsToApply = getComponentsToApply(); + if (componentsToApply != null && componentsToApply.size() > 0) { + final Map fullMap + = fillMissingComponentsWithDefault(componentsToApply); + ThemeManager tm = getThemeManager(); + if (tm != null) { + try { + tm.addClient(ThemeFragment.this); + } catch (IllegalArgumentException e) { + /* ignore since this means we already have a listener added */ + } + ThemeChangeRequest request = + getThemeChangeRequestForComponents(fullMap); + boolean value = request.getReqeustType(). + equals(RequestType.USER_REQUEST_MIXNMATCH); + + tm.requestThemeChange(request, !value); + } + mApplyThemeOnPopulated = false; + } else { + onFinish(true); + } + } + }); + } + } + }; + + protected Map fillMissingComponentsWithDefault( + Map originalMap) { + HashMap newMap = new HashMap(); + newMap.putAll(originalMap); + Map defaultMap = getEmptyComponentsMap(); + for(Map.Entry entry : defaultMap.entrySet()) { + String component = entry.getKey(); + String defaultPkg = entry.getValue(); + if (!newMap.containsKey(component)) { + newMap.put(component, defaultPkg); + } + } + return newMap; + } + + protected Map getEmptyComponentsMap() { + List componentsList = ThemeUtils.getAllComponents(); + Map defaultMap = new HashMap<>(componentsList.size()); + for (String component : componentsList) { + defaultMap.put(component, ""); + } + return defaultMap; + } + + /** + * This is the method that will be called when applying a theme and the idea is to override + * it in MyThemeFragment and pass in a different RequestType, once we have a type that indicates + * the user is mixing and matching instead of applying an entire theme. + * @param componentMap + * @return + */ + protected ThemeChangeRequest getThemeChangeRequestForComponents( + Map componentMap) { + return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST); + } + + protected ThemeChangeRequest getThemeChangeRequestForComponents( + Map componentMap, RequestType requestType) { + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + for (String component : componentMap.keySet()) { + builder.setComponent(component, componentMap.get(component)); + } + builder.setRequestType(requestType); + if (mThemeVersion >= 3) { + builder.setWallpaperId(mSelectedWallpaperComponentId != null + ? mSelectedWallpaperComponentId + : DEFAULT_COMPONENT_ID); + } + return builder.build(); + } + + protected Map getComponentsToApply() { + return mSelectedComponentsMap; + } + + private Runnable mApplyExternalWallpaperRunnable = new Runnable() { + @Override + public void run() { + // If an external image was selected for the wallpaper, we need to + // set that manually. + if (mExternalWallpaperUri != null) { + WallpaperManager wm = + WallpaperManager.getInstance(getActivity()); + final Context context = getActivity(); + final Resources res = context.getResources(); + final Point size = new Point(wm.getDesiredMinimumWidth(), + wm.getDesiredMinimumHeight()); + Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalWallpaperUri, + null, res, 0, 0, false); + try { + wm.setBitmap(bmp); + } catch (Exception e) { + Log.e(TAG, "Unable to set external wallpaper", e); + } + } + } + }; + + private Runnable mApplyExternalLockscreenRunnable = new Runnable() { + @Override + public void run() { + // If an external image was selected for the wallpaper, we need to + // set that manually. + if (mExternalLockscreenUri != null) { + WallpaperManager wm = + WallpaperManager.getInstance(getActivity()); + final Context context = getActivity(); + final Resources res = context.getResources(); + final Point size = new Point(); + ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(size); + Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalLockscreenUri, + null, res, 0, 0, false); + try { + wm.setKeyguardBitmap(bmp); + } catch (Exception e) { + Log.e(TAG, "Unable to set external lockscreen wallpaper", e); + } + } + } + }; + + class RestoreLockScreenCardRunnable implements Runnable { + + final private boolean mWasShowingNone; + private String mCurrentLockScreenPkgName; + + public RestoreLockScreenCardRunnable(boolean w, String pkgName) { + mWasShowingNone = w; + mCurrentLockScreenPkgName = pkgName; + } + + @Override + public void run() { + if (!TextUtils.isEmpty(mCurrentLockScreenPkgName)) { + loadComponentFromPackage(mCurrentLockScreenPkgName, + MODIFIES_LOCKSCREEN, 0); + } else if (mWasShowingNone) { + mLockScreenCard.setWallpaper(null); + TextView none = (TextView) mLockScreenCard.findViewById( + R.id.none); + if (none != null) { + none.setVisibility(View.VISIBLE); + } + mLockScreenCard.setEmptyViewEnabled(false); + } else { + mLockScreenCard.clearWallpaper(); + TextView none = (TextView) mLockScreenCard.findViewById( + R.id.none); + if (none != null) { + none.setVisibility(View.GONE); + } + mLockScreenCard.setEmptyViewEnabled(true); + setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); + } + } + } + + protected void applyTheme() { + if (mExternalWallpaperUri == null && mExternalLockscreenUri == null && + (mSelectedComponentsMap == null || mSelectedComponentsMap.size() <= 0)) { + return; + } + final Map componentsToApply = getComponentsToApply(); + boolean isLLSEnabled = CMSettings.Secure.getInt(getActivity().getContentResolver(), + LIVE_LOCK_SCREEN_ENABLED, 0) == 1; + if (!TextUtils.isEmpty(componentsToApply.get(MODIFIES_LIVE_LOCK_SCREEN)) && !isLLSEnabled) { + AlertDialog d = new AlertDialog.Builder(getActivity(), + android.R.style.Theme_Material_Dialog) + .setTitle(R.string.enable_live_lock_screen_dialog_title) + .setMessage(R.string.enable_live_lock_screen_dialog_message) + .setPositiveButton(R.string.enable_live_lock_screen_dialog_positive_btn_text, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + LIVE_LOCK_SCREEN_ENABLED, 1); + getChooserActivity().themeChangeStart(); + animateProgressIn(mApplyThemeRunnable); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); + boolean wasNone = false; + if (TextUtils.equals("", mSelectedComponentsMap.get( + MODIFIES_LOCKSCREEN))) { + if (!TextUtils.isEmpty(mCurrentTheme.get(MODIFIES_LOCKSCREEN))) { + //The map entry was set to empty string because there's a + //lockscreen currently applied and setting this entry to empty + //would instruct the ThemeManager to clear the currently applied + //lockscreen, but the user decided not to enable LLS so we need + //to abort this change + mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, + mCurrentTheme.get(MODIFIES_LOCKSCREEN)); + } else { + wasNone = true; + } + } + //Restore the lockscreen card to its previous state + mHandler.post(new RestoreLockScreenCardRunnable(wasNone, + mCurrentTheme.get(MODIFIES_LOCKSCREEN))); + + //Did the user make more changes? + boolean modLLS = componentsToApply.containsKey( + MODIFIES_LIVE_LOCK_SCREEN); + boolean modLockscreen = componentsToApply.containsKey( + MODIFIES_LOCKSCREEN); + if (modLLS && ((modLockscreen && componentsToApply.size() > 2) || + (!modLockscreen && componentsToApply.size() > 1))) { + getChooserActivity().themeChangeStart(); + animateProgressIn(mApplyThemeRunnable); + } + } + }) + .setCancelable(false) + .create(); + d.setCanceledOnTouchOutside(false); + d.show(); + } else { + getChooserActivity().themeChangeStart(); + animateProgressIn(mApplyThemeRunnable); + } + } + + /** + * Use when applyTheme() might be too early. ie mSelectedComponentsMap is not pop. yet + * @param pkgName Only used in MyThemeFragment to apply components on top of current theme + * @param components Optional list of components to apply. + */ + protected void applyThemeWhenPopulated(String pkgName, List components) { + mApplyThemeOnPopulated = true; + } + + private void animateProgressIn(Runnable endAction) { + mProgress.setVisibility(View.VISIBLE); + mProgress.setProgress(0); + float pivotX = mTitleLayout.getWidth() - + getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); + ScaleAnimation scaleAnim = new ScaleAnimation(0f, 1f, 1f, 1f, + pivotX, 0f); + scaleAnim.setDuration(ANIMATE_PROGRESS_IN_DURATION); + + mTitleLayout.animate() + .translationXBy(-(pivotX / 3)) + .alpha(0f) + .setDuration(ANIMATE_TITLE_OUT_DURATION) + .setInterpolator(new AccelerateInterpolator()) + .withEndAction(endAction).start(); + mProgress.startAnimation(scaleAnim); + } + + private void animateProgressOut() { + mProgress.setVisibility(View.VISIBLE); + float pivotX = mTitleLayout.getWidth() - + getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); + ScaleAnimation scaleAnim = new ScaleAnimation(1f, 0f, 1f, 1f, + pivotX, 0f); + scaleAnim.setDuration(ANIMATE_PROGRESS_OUT_DURATION); + scaleAnim.setFillAfter(false); + scaleAnim.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + mProgress.setVisibility(View.GONE); + if (mThemeResetting) { + mThemeResetting = false; + mThemeTagLayout.setCustomizedTagEnabled(false); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + + mTitleLayout.animate() + .translationXBy((pivotX / 3)) + .alpha(1f) + .setDuration(ANIMATE_TITLE_IN_DURATION) + .setInterpolator(new AccelerateInterpolator()) + .start(); + mProgress.startAnimation(scaleAnim); + if (mThemeResetting) mReset.setVisibility(View.GONE); + } + + private void animateContentIn() { + if (mSkipLoadingAnim) { + return; + } + AnimatorSet set = new AnimatorSet(); + set.setDuration(ANIMATE_TITLE_IN_DURATION); + set.play(ObjectAnimator.ofFloat(mLoadingView, "alpha", 1f, 0f)) + .with(ObjectAnimator.ofFloat(mTitleLayout, "alpha", 0f, 1f)); + set.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mLoadingView.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + set.start(); + } + + private void disableActionButtons() { + mCustomize.setEnabled(false); + mDelete.setEnabled(false); + mReset.setEnabled(false); + } + + private void enableActionButtons() { + mCustomize.setEnabled(true); + mDelete.setEnabled(true); + mReset.setEnabled(true); + } + + public boolean isShowingConfirmCancelOverlay() { + return mConfirmCancelOverlay.getVisibility() == View.VISIBLE; + } + + public void showApplyThemeOverlay() { + if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; + mConfirmCancelOverlay.setTitle(R.string.apply_theme_overlay_title); + mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() + .getColor(R.color.apply_overlay_background)); + mConfirmCancelOverlay.setOnOverlayDismissedListener(mApplyCancelListener); + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + mConfirmCancelOverlay.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + if (mIsLegacyTheme) { + // Display cm11 theme warning message + TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); + tv.setVisibility(View.VISIBLE); + tv.setText(String.format(getString(R.string.legacy_theme_warning), mTitle.getText())); + } else if (Utils.hasPerAppThemesApplied(getActivity())) { + // Display per app theme changes will be removed warning + TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); + tv.setVisibility(View.VISIBLE); + tv.setText(String.format(getString(R.string.per_app_theme_removal_warning), + mTitle.getText())); + } + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void showDeleteThemeOverlay() { + if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; + mConfirmCancelOverlay.setTitle(R.string.delete_theme_overlay_title); + mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() + .getColor(R.color.delete_overlay_background)); + mConfirmCancelOverlay.setOnOverlayDismissedListener(mDeleteConfirmationListener); + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + mConfirmCancelOverlay.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void showResetThemeOverlay() { + if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; + mConfirmCancelOverlay.setTitle(R.string.reset_theme_overlay_title); + mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() + .getColor(R.color.apply_overlay_background)); + mConfirmCancelOverlay.setOnOverlayDismissedListener(mResetConfirmationListener); + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + mConfirmCancelOverlay.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void hideConfirmCancelOverlay() { + hideConfirmCancelOverlay(false); + } + + /** + * Hides the apply theme layout overlay and can apply the selected theme + * when the animation is finished. + * @param applyThemeWhenFinished If true, the current theme will be applied. + */ + private void hideConfirmCancelOverlay(final boolean applyThemeWhenFinished) { + getChooserActivity().unlockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(0f).start(); + anim.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mConfirmCancelOverlay.setVisibility(View.GONE); + if (applyThemeWhenFinished) applyTheme(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + + enableActionButtons(); + mClickableView.setSoundEffectsEnabled(true); + } + + public boolean isShowingCustomizeResetLayout() { + return mCustomizeResetLayout.getVisibility() == View.VISIBLE; + } + + public void showCustomizeResetLayout() { + if (mCustomizeResetLayout.getVisibility() == View.VISIBLE) return; + if (!mThemeTagLayout.isCustomizedTagEnabled()) { + mResetButton.setEnabled(false); + } else { + mResetButton.setEnabled(true); + } + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); + mCustomizeResetLayout.setVisibility(View.VISIBLE); + mCustomizeResetLayout.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void hideCustomizeResetLayout() { + hideCustomizeResetLayout(CustomizeResetAction.Dismiss); + } + + private void hideCustomizeResetLayout(final CustomizeResetAction action) { + getChooserActivity().unlockPager(); + ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); + mCustomizeResetLayout.setVisibility(View.VISIBLE); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(0f).start(); + anim.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mCustomizeResetLayout.setVisibility(View.GONE); + switch (action) { + case Customize: + getChooserActivity().expand(); + break; + case Reset: + resetTheme(); + break; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + + enableActionButtons(); + mClickableView.setSoundEffectsEnabled(true); + } + + public void showThemeTagLayout() { + mThemeTagLayout.setVisibility(View.VISIBLE); + mThemeTagLayout.animate().alpha(1f).setStartDelay(ANIMATE_START_DELAY).start(); + } + + public void hideThemeTagLayout() { + mThemeTagLayout.setAlpha(0f); + mThemeTagLayout.setVisibility(View.GONE); + } + + public void hideProcessingOverlay() { + mProcessingThemeLayout.animate().alpha(0).withEndAction(new Runnable() { + @Override + public void run() { + mProcessingThemeLayout.setVisibility(View.GONE); + } + }).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); + mCustomize.setVisibility(View.VISIBLE); + mCustomize.setAlpha(0f); + mCustomize.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); + if (mDelete.getVisibility() != View.GONE) { + mDelete.setVisibility(View.VISIBLE); + mDelete.setAlpha(0f); + mDelete.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); + } + + enableActionButtons(); + mClickableView.setSoundEffectsEnabled(true); + } + + public void fadeInCards() { + for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { + final int key = mCardIdsToComponentTypes.keyAt(i); + if (key != mActiveCardId) { + ComponentCardView card = (ComponentCardView) getView().findViewById(key); + if (card != null) card.animateCardFadeIn(); + } + } + mActiveCardId = -1; + } + + public boolean componentsChanged() { + // If an external wallpaper/ls are set then something changed! + if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; + + for (String key : mSelectedComponentsMap.keySet()) { + if (!mPkgName.equals(mSelectedComponentsMap.get(key))) { + return true; + } + if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && + mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { + return true; + } + } + return false; + } + + protected boolean isThemeCustomized() { + final String themePkgName = getThemePackageName(); + for (String key : mSelectedComponentsMap.keySet()) { + final String selectedPkgName = mSelectedComponentsMap.get(key); + if (!themePkgName.equals(selectedPkgName)) { + return true; + } + if (mBaseThemeSupportedComponents.size() > 0 && + !mBaseThemeSupportedComponents.contains(key)) { + return true; + } + } + // finally check if we're missing anything from mBaseThemeSupportedComponents + for (String component : mBaseThemeSupportedComponents) { + if (!mSelectedComponentsMap.containsKey(component)) return true; + } + return false; + } + + public void clearChanges() { + mSelectedComponentsMap.clear(); + mExternalWallpaperUri = null; + mExternalLockscreenUri = null; + View none = mLockScreenCard.findViewById(R.id.none); + if (none != null && none.getVisibility() == View.VISIBLE) { + none.setVisibility(View.GONE); + } + TextView tv = (TextView) mLockScreenCard.findViewById(R.id.label); + if (tv != null) { + tv.setAlpha(1f); + tv.setBackgroundResource(R.drawable.wallpaper_label_bg); + } + getLoaderManager().restartLoader(LOADER_ID_ALL, null, ThemeFragment.this); + } + + public String getThemePackageName() { + if (mPkgName == null) { + // check if the package name is defined in the arguments bundle + Bundle bundle = getArguments(); + if (bundle != null) { + mPkgName = bundle.getString(ARG_PACKAGE_NAME); + } + } + return mPkgName; + } + + private void uninstallTheme() { + getChooserActivity().uninstallTheme(mPkgName); + } + + public void setCurrentTheme(Map currentTheme, + MutableLong currentWallpaperComponentId) { + mCurrentTheme = currentTheme; + mCurrentWallpaperComponentId = currentWallpaperComponentId; + } + + /** + * Slides the scrollview content up and adds a space view at the bottom + * of mAdditionalCards so all content can be visible above the selector. + * + * We are using a ValueAnimator here to scroll the content rather than calling + * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. + * @param yDelta + * @param selectorHeight + */ + public void slideContentIntoView(final int yDelta, int selectorHeight) { + Space space = (Space) mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); + if (space == null) { + // No space view yet so lets create it one + space = new Space(getActivity()); + space.setId(ADDITIONAL_CONTENT_SPACE_ID); + mAdditionalCards.addView(space, + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + selectorHeight)); + } else { + // Space view already exists so just update the LayoutParams + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) space.getLayoutParams(); + params.height = selectorHeight; + space.setLayoutParams(params); + } + final int startY = mScrollView.getScrollY(); + final ValueAnimator scrollAnimator = + ValueAnimator.ofInt(startY, startY + yDelta); + scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); + scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mScrollView.scrollTo(0, value); + } + }); + scrollAnimator.start(); + } + + public Map getSelectedComponentsMap() { + return mSelectedComponentsMap; + } + + /** + * Slides the scrollview content down and removes a space view at the bottom + * of mAdditionalCards. + * + * We are using a ValueAnimator here to scroll the content rather than calling + * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. + * @param yDelta + */ + public void slideContentBack(int yDelta) { + final int startY = mScrollView.getScrollY(); + final ValueAnimator scrollAnimator = + ValueAnimator.ofInt(startY, startY + yDelta); + scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); + scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mScrollView.scrollTo(0, value); + } + }); + scrollAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + View space = mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); + if (space != null) mAdditionalCards.removeView(space); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + scrollAnimator.start(); + } + + public void showLockScreenCard() { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + final int scrollToY = mStatusBarCard.getMeasuredHeight() + + mFontCard.getMeasuredHeight() + mIconCard.getMeasuredHeight() + + mNavBarCard.getMeasuredHeight() + mWallpaperCard.getMeasuredHeight() / 2; + final ValueAnimator scrollAnimator = ValueAnimator.ofInt(0, scrollToY); + scrollAnimator.setDuration(LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION); + scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mScrollView.scrollTo(0, value); + } + }); + scrollAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mCardClickListener.onClick(mLockScreenCard); + } + }); + scrollAnimator.start(); + } + }, SHOW_LOCK_SCREEN_CARD_DELAY); + } + + protected void startLiveLockScreenSettings() { + Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + // TODO: inform user that this action failed (Toast?) + } + } + + class AnimationLoader extends AsyncTask { + Context mContext; + String mPkgName; + BootAniImageView mBootAnim; + + public AnimationLoader(Context context, String pkgName, BootAniImageView bootAnim) { + mContext = context; + mPkgName = pkgName; + mBootAnim = bootAnim; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + @Override + protected Boolean doInBackground(Void... params) { + if (mContext == null) { + return Boolean.FALSE; + } + ZipFile zip = null; + if (ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { + try { + zip = new ZipFile(new File(BootAnimationHelper.SYSTEM_BOOT_ANI_PATH)); + } catch (Exception e) { + Log.w(TAG, "Unable to load boot animation", e); + return Boolean.FALSE; + } + } else { + // check if the bootanimation is cached + File f = new File(mContext.getCacheDir(), + mPkgName + BootAnimationHelper.CACHED_SUFFIX); + if (!f.exists()) { + // go easy on cache storage and clear out any previous boot animations + BootAnimationHelper.clearBootAnimationCache(mContext); + try { + Context themeContext = mContext.createPackageContext(mPkgName, 0); + AssetManager am = themeContext.getAssets(); + InputStream is = am.open("bootanimation/bootanimation.zip"); + FileUtils.copyToFile(is, f); + is.close(); + } catch (Exception e) { + Log.w(TAG, "Unable to load boot animation", e); + return Boolean.FALSE; + } + } + try { + zip = new ZipFile(f); + } catch (IOException e) { + Log.w(TAG, "Unable to load boot animation", e); + return Boolean.FALSE; + } + } + if (zip != null) { + mBootAnim.setBootAnimation(zip); + } else { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + @Override + protected void onPostExecute(Boolean isSuccess) { + super.onPostExecute(isSuccess); + if (isSuccess) { + mBootAnim.start(); + } + } + } + + class AudibleLoadingThread extends Thread { + private Context mContext; + private int mType; + private String mPkgName; + private MediaPlayer mPlayer; + + public AudibleLoadingThread(Context context, int type, String pkgName, MediaPlayer mp) { + super(); + mContext = context; + mType = type; + mPkgName = pkgName; + mPlayer = mp; + } + + @Override + public void run() { + try { + AudioUtils.loadThemeAudible(mContext, mType, mPkgName, mPlayer); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to load sound for " + mPkgName, e); + } + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/WallpaperCardView.java b/src/org/cyanogenmod/theme/chooser/WallpaperCardView.java new file mode 100644 index 0000000..a3ed8ea --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/WallpaperCardView.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewOverlay; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +public class WallpaperCardView extends ComponentCardView { + protected ImageView mImage; + protected TextView mLabel; + + public WallpaperCardView(Context context) { + this(context, null); + } + + public WallpaperCardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public WallpaperCardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WallpaperCardView); + String labelText = a.getString(R.styleable.WallpaperCardView_labelText); + a.recycle(); + + setOrientation(VERTICAL); + + setBackgroundResource(R.drawable.card_bg); + + LayoutInflater inflater = LayoutInflater.from(mContext); + FrameLayout frameLayout = + (FrameLayout) inflater.inflate(R.layout.wallpaper_card, this, false); + addView(frameLayout); + mLabel = (TextView) frameLayout.findViewById(R.id.label); + mImage = (ImageView) frameLayout.findViewById(R.id.image); + + mLabel.setText(labelText); + } + + public void setWallpaper(Drawable drawable) { + mImage.setImageDrawable(drawable); + View none = findViewById(R.id.none); + if (drawable == null) { + setBackgroundResource(R.drawable.card_wallpapertoggled_bg); + if (none != null) { + none.setVisibility(View.VISIBLE); + } + if (mLabel != null) { + mLabel.setBackgroundResource(0); + } + } else { + setBackgroundResource(0); + + if (none != null) { + none.setVisibility(View.GONE); + } + if (mLabel != null) { + mLabel.setBackgroundResource(R.drawable.wallpaper_label_bg); + } + } + } + + public void clearWallpaper() { + mImage.setImageDrawable(null); + setBackgroundResource(R.drawable.card_bg); + } + + public Drawable getWallpaperDrawable() { + return mImage.getDrawable(); + } + + @Override + public void expand(boolean showLabel) { + setEnabled(true); + } + + @Override + public void collapse() { + setEnabled(false); + } + + /** + * Animates a change in the content of the card + * @param v View in card to animate + * @param overlay Drawable to animate as a ViewOverlay + * @param duration Duration of animation + */ + @Override + public void animateContentChange(View v, final Drawable overlay, long duration) { + // Since the wallpaper IS the content, we will ignore the view passed in and animate + // the entire card + final ViewOverlay viewOverlay = this.getOverlay(); + viewOverlay.add(overlay); + final int x = 0; + final int y = 0; + final int width = v.getWidth(); + final int height = v.getHeight(); + overlay.setBounds(x, y, x + width, y + height); + + final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); + overlayAnimator.setDuration(duration); + overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + overlay.setAlpha((int) (255 * value)); + } + + }); + overlayAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + // Clear out the ViewOverlay now that we are done animating + viewOverlay.clear(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(overlay, "alpha", 0f, 1f)) + .with(overlayAnimator); + set.start(); + } +} diff --git a/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java new file mode 100644 index 0000000..424d640 --- /dev/null +++ b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.perapptheming; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.PointF; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Animation; +import android.widget.FrameLayout; + +import org.cyanogenmod.theme.chooser.R; + +public class PerAppThemeListLayout extends FrameLayout { + private PerAppThemingWindow mWindow; + private PointF mCenter; + + private float mMaxRadius; + private float mTargetRadius; + private float mStartRadius; + private float mCurrentRadius; + + private ValueAnimator mAnimator; + private boolean mIsAnimating; + + private Path mRevealPath; + + public PerAppThemeListLayout(Context context) { + this(context, null); + } + + public PerAppThemeListLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PerAppThemeListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + final Resources res = getResources(); + float width = res.getDimension(R.dimen.theme_list_width); + float height = res.getDimension(R.dimen.theme_list_max_height); + mMaxRadius = (float) Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + mRevealPath = new Path(); + + mAnimator = new ValueAnimator(); + mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + mAnimator.addListener(mAnimationListener); + mAnimator.addUpdateListener(mUpdateListener); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && + event.getKeyCode() == KeyEvent.KEYCODE_BACK && mWindow != null) { + mWindow.hideThemeList(); + } + return super.dispatchKeyEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isEnabled() && event.getAction() == MotionEvent.ACTION_DOWN && mWindow != null) { + mWindow.hideThemeList(); + return true; + } + return super.onTouchEvent(event); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (!mIsAnimating) { + super.dispatchDraw(canvas); + } else { + final int state = canvas.save(); + mRevealPath.reset(); + mRevealPath.addCircle(mCenter.x, mCenter.y, mCurrentRadius, Path.Direction.CW); + canvas.clipPath(mRevealPath); + super.dispatchDraw(canvas); + canvas.restoreToCount(state); + } + } + + public void setPerAppThemingWindow(PerAppThemingWindow window) { + mWindow = window; + } + + /** + * Perform a circular reveal from center cx,cy + * @param cx X position of center + * @param cy Y position of center + * @param duration Duration of animation + */ + public void circularReveal(float cx, float cy, long duration) { + mCenter = new PointF(cx, cy); + mIsAnimating = true; + + mStartRadius = mCurrentRadius; + mTargetRadius = mMaxRadius; + startAnimation(duration); + } + + /** + * Perform a circular hide from center cx,cy + * @param cx X position of center + * @param cy Y position of center + * @param duration Duration of animation + */ + public void circularHide(float cx, float cy, long duration) { + mCenter = new PointF(cx, cy); + mIsAnimating = true; + + mStartRadius = mCurrentRadius; + mTargetRadius = 0f; + startAnimation(duration); + } + + private void startAnimation(long duration) { + getChildAt(0).setVisibility(View.VISIBLE); + mAnimator.setFloatValues(mStartRadius, mTargetRadius); + mAnimator.setDuration(duration); + mAnimator.start(); + } + + private ValueAnimator.AnimatorUpdateListener mUpdateListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Float value = (Float) animation.getAnimatedValue(); + mCurrentRadius = value.floatValue(); + invalidate(); + } + }; + + private Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mIsAnimating = false; + if (mCurrentRadius <= 0) { + getChildAt(0).setVisibility(INVISIBLE); + } + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; +} diff --git a/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java new file mode 100644 index 0000000..9986f60 --- /dev/null +++ b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.perapptheming; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.ListView; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +public class PerAppThemeListView extends ListView { + private int mMinHeight; + private int mMaxHeight; + + public PerAppThemeListView(Context context) { + this(context, null); + } + + public PerAppThemeListView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PerAppThemeListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + final Resources res = getResources(); + TypedArray a = context.obtainStyledAttributes(attrs, + Utils.getResourceDeclareStyleableIntArray("com.android.internal", "View")); + int resId = res.getIdentifier("View_minHeight", "styleable", "android"); + mMinHeight = a.getDimensionPixelSize(resId, 0); + a.recycle(); + + a = context.obtainStyledAttributes(attrs, R.styleable.PerAppThemeListView); + mMaxHeight = a.getDimensionPixelSize(R.styleable.PerAppThemeListView_maxHeight, 0); + a.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // let the super do the heavy lifting and then we'll cap the values to any max and/or min + // values that were defined in the layout + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int measuredWidth = getMeasuredWidth(); + int measuredHeight = getMeasuredHeight(); + int newHeight = measuredHeight; + if (mMaxHeight > 0) { + newHeight = Math.min(measuredHeight, mMaxHeight); + } + if (mMinHeight > 0) { + newHeight = Math.max(newHeight, mMinHeight); + } + if (newHeight != measuredHeight) { + setMeasuredDimension(measuredWidth, newHeight); + } + } +} diff --git a/src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java b/src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java new file mode 100644 index 0000000..27db329 --- /dev/null +++ b/src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java @@ -0,0 +1,1078 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.perapptheming; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.ContentObserver; +import android.database.Cursor; +import android.graphics.PixelFormat; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeChangeRequest; +import cyanogenmod.themes.ThemeManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class PerAppThemingWindow extends Service implements OnTouchListener, + ThemeManager.ThemeChangeListener { + // Animation frame rate per second + private static final int ANIMATION_FRAME_RATE = 60; + + private static final int EXIT_DELETE_MODE_ANIMATION_DURATION = 50; + + private static final int MOVE_TO_DELETE_BOX_ANIMATION_DURATION = 150; + + private static final int ANIMATION_DURATION = 300; + + private static final int FAB_SCALE_ANIMATION_DURATION = 150; + + private static final int LIST_ON_LEFT_SIDE = 0; + private static final int LIST_ON_RIGHT_SIDE = 1; + + // Don't want these colors to be themable and possibly alter the effect we are after, so + // they are defined here rather than in colors.xml + private static final int SCRIM_COLOR_TRANSPARENT = 0x00000000; + private static final int SCRIM_COLOR_OPAQUE = 0xaa000000; + + // Amount to wait after a theme change occurred before fading the scrim away + // This value was obtained empirically by performing theme changes and adjusting this delay + private static final int THEME_CHANGE_DELAY = 1500; + + private static final float PRESSED_FAB_SCALE = 0.95f; + + private static final float DELETE_BOX_ANIMATION_SCALE = 0.3f; + + private static final int MAX_DEPRECIATION = 5; + + private static final float FAB_ANIMATION_SCALE_FACTOR = 0.44f; + + // Margin around the phone + private static int MARGIN_VERTICAL; + // Margin around the phone + private static int MARGIN_HORIZONTAL; + private static int CLOSE_ANIMATION_DISTANCE; + private static int DRAG_DELTA; + private static int STARTING_POINT_Y; + private static int DELETE_BOX_WIDTH; + private static int DELETE_BOX_HEIGHT; + private static int FLOATING_WINDOW_ICON_SIZE; + + // View variables + private BroadcastReceiver mBroadcastReceiver; + private WindowManager mWindowManager; + private LinearLayout mDraggableIcon; + private View mDraggableIconImage; + private WindowManager.LayoutParams mParams; + private PerAppThemeListLayout mThemeListLayout; + private WindowManager.LayoutParams mListLayoutParams; + private ListView mThemeList; + private ThemesAdapter mAdapter; + private FrameLayout.LayoutParams mListParams; + private LinearLayout mDeleteView; + private View mDeleteBoxView; + private View mThemeApplyingView; + private boolean mDeleteBoxVisible = false; + private boolean mIsDestroyed = false; + private boolean mIsBeingDestroyed = false; + private int mCurrentPosX = -1; + + // Animation variables + private List mDeltaXArray; + private List mDeltaYArray; + private AnimationTask mAnimationTask; + + // Close logic + private int mCurrentX; + private int mCurrentY; + private boolean mIsInDeleteMode = false; + private boolean mIsAnimationLocked = false; + + // Drag variables + float mPrevDragX; + float mPrevDragY; + float mOrigX; + float mOrigY; + boolean mDragged; + + private int mListSide = LIST_ON_LEFT_SIDE; + + private ThemeConfig mThemeConfig; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + // Load margins, distances, etc. + final Resources res = getResources(); + MARGIN_VERTICAL = + res.getDimensionPixelSize(R.dimen.floating_window_margin_vertical); + MARGIN_HORIZONTAL = + res.getDimensionPixelSize(R.dimen.floating_window_margin_horizontal); + CLOSE_ANIMATION_DISTANCE = + res.getDimensionPixelSize(R.dimen.floating_window_close_animation_distance); + DRAG_DELTA = res.getDimensionPixelSize(R.dimen.floating_window_drag_delta); + STARTING_POINT_Y = res.getDimensionPixelSize(R.dimen.floating_window_starting_point_y); + + DELETE_BOX_WIDTH = (int) getResources().getDimension( + R.dimen.floating_window_delete_box_width); + DELETE_BOX_HEIGHT = (int) getResources().getDimension( + R.dimen.floating_window_delete_box_height); + FLOATING_WINDOW_ICON_SIZE = (int) getResources().getDimension( + R.dimen.floating_window_icon); + + mDeleteView = new LinearLayout(getContext()); + View.inflate(getContext(), R.layout.per_app_delete_box_window, mDeleteView); + mDeleteBoxView = mDeleteView.findViewById(R.id.box); + addView(mDeleteView, 0, 0, Gravity.BOTTOM | Gravity.CENTER_VERTICAL, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT); + mDeleteView.setVisibility(View.GONE); + + mDraggableIcon = new LinearLayout(this); + mDraggableIcon.setOnTouchListener(this); + View.inflate(getContext(), R.layout.per_app_fab_floating_window_icon, mDraggableIcon); + mDraggableIconImage = mDraggableIcon.findViewById(R.id.box); + mDraggableIconImage.setClipToOutline(true); + mDraggableIconImage.getViewTreeObserver().addOnWindowAttachListener(mWindowAttachListener); + mParams = addView(mDraggableIcon, 0, 0); + updateIconPosition(MARGIN_HORIZONTAL, STARTING_POINT_Y); + + mThemeListLayout = (PerAppThemeListLayout) View.inflate(getContext(), + R.layout.per_app_theme_list, null); + mThemeListLayout.setPerAppThemingWindow(this); + mThemeList = (ListView) mThemeListLayout.findViewById(R.id.theme_list); + mListParams = (FrameLayout.LayoutParams) mThemeList.getLayoutParams(); + mThemeApplyingView = mThemeListLayout.findViewById(R.id.applying_theme_text); + + final Configuration config = getResources().getConfiguration(); + mThemeConfig = getThemeConfig(config); + loadThemes(); + getContentResolver().registerContentObserver(ThemesColumns.CONTENT_URI, true, + mThemesObserver); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mIsBeingDestroyed) return true; + + switch(event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mThemeListLayout.isAttachedToWindow()) { + hideThemeList(); + return false; + } + mPrevDragX = mOrigX = event.getRawX(); + mPrevDragY = mOrigY = event.getRawY(); + + mDragged = false; + + mDeltaXArray = new LinkedList(); + mDeltaYArray = new LinkedList(); + + mCurrentX = mParams.x; + mCurrentY = mParams.y; + + mDraggableIconImage.setScaleX(PRESSED_FAB_SCALE); + mDraggableIconImage.setScaleY(PRESSED_FAB_SCALE); + + // Cancel any currently running animations + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + break; + case MotionEvent.ACTION_UP: + mIsAnimationLocked = false; + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + + if (!mDragged) { + // clicked so show theme list + final int mid = getScreenWidth() / 2; + mListSide = LIST_ON_LEFT_SIDE; + if (mCurrentPosX > mid) mListSide = LIST_ON_RIGHT_SIDE; + if (!mThemeListLayout.isAttachedToWindow()) showThemeList(); + } else { + // Animate the icon + mAnimationTask = new AnimationTask(); + mAnimationTask.run(); + } + + if (mIsInDeleteMode) { + close(true); + } else { + hideDeleteBox(); + mDraggableIconImage.setScaleX(1f); + mDraggableIconImage.setScaleY(1f); + } + break; + case MotionEvent.ACTION_MOVE: + mCurrentX = (int) (event.getRawX() - mDraggableIcon.getWidth() / 2); + mCurrentY = (int) (event.getRawY() - mDraggableIcon.getHeight()); + if (isDeleteMode()) { + mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); + mIsInDeleteMode = true; + updateIconPosition(mCurrentX, mCurrentY); + } else if (mIsInDeleteMode){ + mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_normal); + mIsInDeleteMode = false; + } else { + if(!mIsAnimationLocked && mDragged) { + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + updateIconPosition(mCurrentX, mCurrentY); + } + } + + float deltaX = event.getRawX() - mPrevDragX; + float deltaY = event.getRawY() - mPrevDragY; + + mDeltaXArray.add(deltaX); + mDeltaYArray.add(deltaY); + + mPrevDragX = event.getRawX(); + mPrevDragY = event.getRawY(); + + deltaX = event.getRawX() - mOrigX; + deltaY = event.getRawY() - mOrigY; + mDragged = mDragged || Math.abs(deltaX) > DRAG_DELTA + || Math.abs(deltaY) > DRAG_DELTA; + if (mDragged) { + showDeleteBox(); + } + break; + } + + return true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mIsDestroyed = true; + if (mDraggableIcon != null) { + removeViewIfAttached(mDraggableIcon); + mDraggableIcon = null; + } + if (mDeleteView != null) { + removeViewIfAttached(mDeleteView); + mDeleteView = null; + } + if (mThemeListLayout != null) { + removeViewIfAttached(mThemeListLayout); + mThemeListLayout = null; + } + if (mAnimationTask != null) { + mAnimationTask.cancel(); + mAnimationTask = null; + } + if (mBroadcastReceiver != null) { + unregisterReceiver(mBroadcastReceiver); + mBroadcastReceiver = null; + } + if (mThemesObserver != null) { + getContentResolver().unregisterContentObserver(mThemesObserver); + mThemesObserver = null; + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mThemeConfig = getThemeConfig(newConfig); + } + + @Override + public void onProgress(int progress) { + } + + @Override + public void onFinish(boolean isSuccess) { + ThemeManager tm = ThemeManager.getInstance(getContext()); + tm.removeClient(this); + mThemeListLayout.postDelayed(new Runnable() { + @Override + public void run() { + hideScrim(); + startFabScaleUpAnimation(); + } + }, THEME_CHANGE_DELAY); + } + + public void hideThemeList() { + hideThemeList(false, new Runnable() { + @Override + public void run() { + removeViewIfAttached(mThemeListLayout); + } + }); + } + + private ThemeConfig getThemeConfig(Configuration config) { + if (config != null && config.themeConfig != null) { + return config.themeConfig; + } + + return ThemeConfig.getBootTheme(getContentResolver()); + } + + private void removeViewIfAttached(View view) { + if (view.isAttachedToWindow()) { + mWindowManager.removeViewImmediate(view); + } + } + + private WindowManager.LayoutParams addView(View v, int x, int y) { + return addView(v, x, y, Gravity.TOP | Gravity.LEFT, + WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); + } + + private WindowManager.LayoutParams addView(View v, int x, int y, int gravity, + int width, int height) { + mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + WindowManager.LayoutParams params = new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); + + params.gravity = gravity; + params.x = x; + params.y = y; + + mWindowManager.addView(v, params); + + return params; + } + + private void updateIconPosition(int x, int y) { + mCurrentPosX = x; + + View v = mDraggableIconImage; + v.setTranslationX(0); + if (x < 0) { + v.setTranslationX(x); + x = 0; + } + + if (x > getScreenWidth() - FLOATING_WINDOW_ICON_SIZE) { + v.setTranslationX(x - getScreenWidth() + FLOATING_WINDOW_ICON_SIZE); + x = getScreenWidth() - FLOATING_WINDOW_ICON_SIZE; + } + + v.setTranslationY(0); + if (y < 0) { + v.setTranslationY(y); + y = 0; + } + + if (y > getScreenHeight() - FLOATING_WINDOW_ICON_SIZE) { + v.setTranslationY(y - getScreenHeight() + FLOATING_WINDOW_ICON_SIZE); + y = getScreenHeight() - FLOATING_WINDOW_ICON_SIZE; + } + mParams.x = x; + mParams.y = y; + + if (!mIsDestroyed) { + mWindowManager.updateViewLayout(mDraggableIcon, mParams); + } + } + + private boolean isDeleteMode() { + return isHoveringOverDeleteBox(mParams.y); + } + + private boolean isHoveringOverDeleteBox(int y) { + return y + mDraggableIconImage.getHeight() >= getScreenHeight() - DELETE_BOX_HEIGHT; + } + + private void showDeleteBox() { + if (!mDeleteBoxVisible) { + mDeleteBoxVisible = true; + mDeleteView.setVisibility(View.VISIBLE); + + mDeleteBoxView.setAlpha(0); + mDeleteBoxView.setTranslationY(CLOSE_ANIMATION_DISTANCE); + mDeleteBoxView.animate().alpha(1).translationYBy(-1 * CLOSE_ANIMATION_DISTANCE) + .setListener(null); + + mDeleteBoxView.getLayoutParams().width = getScreenWidth(); + } + } + + private void hideDeleteBox() { + if (mDeleteBoxVisible) { + mDeleteBoxVisible = false; + if (mDeleteView != null) { + mDeleteBoxView.animate().alpha(0) + .translationYBy(CLOSE_ANIMATION_DISTANCE) + .setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mDeleteView != null) mDeleteView.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + } + } + + private void animateToDeleteBoxCenter(final OnAnimationFinishedListener l) { + if (mIsAnimationLocked) { + return; + } + mIsInDeleteMode = true; + + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + + mAnimationTask = new AnimationTask(getScreenWidth() / 2 - mDraggableIcon.getWidth() / 2, + getScreenHeight() - DELETE_BOX_HEIGHT / 2 - mDraggableIcon.getHeight() / 2); + mAnimationTask.setDuration(MOVE_TO_DELETE_BOX_ANIMATION_DURATION); + mAnimationTask.setAnimationFinishedListener(l); + mAnimationTask.run(); + mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); + } + + private void close(boolean animate) { + if (mIsBeingDestroyed) { + return; + } + mIsBeingDestroyed = true; + + if (animate) { + animateToDeleteBoxCenter(new OnAnimationFinishedListener() { + @Override + public void onAnimationFinished() { + hideDeleteBox(); + mDeleteBoxView.animate() + .scaleX(DELETE_BOX_ANIMATION_SCALE) + .scaleY(DELETE_BOX_ANIMATION_SCALE); + mDraggableIconImage.animate() + .scaleX(DELETE_BOX_ANIMATION_SCALE) + .scaleY(DELETE_BOX_ANIMATION_SCALE) + .translationY(CLOSE_ANIMATION_DISTANCE) + .setDuration(mDeleteBoxView.animate().getDuration()) + .setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + stopSelf(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + }); + } else { + stopSelf(); + } + } + + private static interface OnAnimationFinishedListener { + public void onAnimationFinished(); + } + + private Context getContext() { + return this; + } + + private int getScreenWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + private int getScreenHeight() { + return getResources().getDisplayMetrics().heightPixels - getStatusBarHeight(); + } + + private int getStatusBarHeight() { + int result = 0; + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = getResources().getDimensionPixelSize(resourceId); + } + + return result; + } + + private void loadThemes() { + String[] columns = {ThemesColumns._ID, ThemesColumns.TITLE, ThemesColumns.PKG_NAME}; + String selection = ThemesColumns.MODIFIES_OVERLAYS + "=? AND " + + ThemesColumns.INSTALL_STATE + "=?"; + String[] selectionArgs = {"1", "" + ThemesColumns.InstallState.INSTALLED}; + String sortOrder = ThemesColumns.TITLE + " ASC"; + Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, columns, selection, + selectionArgs, sortOrder); + if (c != null) { + if (mAdapter == null) { + mAdapter = new ThemesAdapter(this, c); + mThemeList.setAdapter(mAdapter); + mThemeList.setOnItemClickListener(mThemeClickedListener); + } else { + String pkgName = (String) mAdapter.getItem(0); + mAdapter.populateThemes(c); + mAdapter.setCurrentTheme(pkgName); + } + } + } + + private ContentObserver mThemesObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + loadThemes(); + } + }; + + private ViewTreeObserver.OnWindowAttachListener mWindowAttachListener = + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + // Remove the this OnWindowAttachListener now that we are done with it. + mDraggableIconImage.getViewTreeObserver().removeOnWindowAttachListener(this); + + final float fabWidth = getResources().getDimension(R.dimen.floating_window_icon); + mDraggableIconImage.setAlpha(0); + mDraggableIconImage.setX(-fabWidth); + mDraggableIconImage.animate() + .alpha(1f) + .xBy(fabWidth) + .setDuration(ANIMATION_DURATION) + .start(); + } + + @Override + public void onWindowDetached() { + } + }; + + private void showThemeList() { + if (mListLayoutParams == null) { + mListLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + } + mListLayoutParams.gravity = Gravity.TOP | + (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); + mWindowManager.addView(mThemeListLayout, mListLayoutParams); + + setThemeListPosition(); + startFabScaleDownAnimation(); + + mAdapter.setCurrentTheme( + mThemeConfig.getOverlayPkgNameForApp(Utils.getTopTaskPackageName(this))); + mThemeListLayout.circularReveal(mParams.x + mDraggableIconImage.getWidth() / 2, + mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); + } + + private void hideThemeList(boolean showScrim, final Runnable endAction) { + if (showScrim) { + showScrim(); + } else { + startFabScaleUpAnimation(); + } + mThemeListLayout.circularHide(mParams.x + mDraggableIconImage.getWidth() / 2, + mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); + if (endAction != null) { + mDraggableIcon.postDelayed(endAction, ANIMATION_DURATION); + } + } + + private void showScrim() { + ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_TRANSPARENT, + SCRIM_COLOR_OPAQUE); + mThemeListLayout.setEnabled(false); + animator.setDuration(ANIMATION_DURATION) + .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Integer value = (Integer) animation.getAnimatedValue(); + mThemeListLayout.setBackgroundColor(value.intValue()); + } + }); + animator.start(); + mThemeApplyingView.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION); + } + + private void hideScrim() { + ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_OPAQUE, SCRIM_COLOR_TRANSPARENT); + animator.setDuration(ANIMATION_DURATION) + .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Integer value = (Integer) animation.getAnimatedValue(); + mThemeListLayout.setBackgroundColor(value.intValue()); + } + }); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + removeViewIfAttached(mThemeListLayout); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + animator.start(); + mThemeApplyingView.animate() + .alpha(0f) + .setDuration(ANIMATION_DURATION); + mDraggableIcon.setVisibility(View.VISIBLE); + mDraggableIconImage.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION); + } + + private void setThemeListPosition() { + int thirdHeight = getScreenHeight() / 3; + // use the center of the fab to decide where to place the list + int fabLocationY = mParams.y + mDraggableIconImage.getHeight() / 2; + int listHeight = mThemeList.getMeasuredHeight(); + if (listHeight <= 0) { + // not measured yet so let's force that + int width = getResources().getDimensionPixelSize(R.dimen.theme_list_width); + int height = getResources().getDimensionPixelSize(R.dimen.theme_list_max_height); + mThemeList.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)); + listHeight = mThemeList.getMeasuredHeight(); + } + + // If we're in the top 1/3 of the screen position the top of the list with the top + // of the fab. Second 3rd will position the list so that it is vertically centered + // with the fab center. Bottom 3rd will position the bottom of the list with the + // bottom of the fab. + if (fabLocationY < thirdHeight) { + mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2; + } else if (fabLocationY < thirdHeight * 2) { + mListParams.topMargin = fabLocationY - listHeight / 2; + } else { + mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2 - listHeight; + } + mListParams.gravity = Gravity.TOP | + (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); + mThemeList.setLayoutParams(mListParams); + } + + private void startFabScaleDownAnimation() { + final int iconWidth = mDraggableIconImage.getWidth(); + final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * + (mListSide == LIST_ON_LEFT_SIDE ? -1 : 1); + + mDraggableIconImage.animate() + .scaleX(FAB_ANIMATION_SCALE_FACTOR) + .scaleY(FAB_ANIMATION_SCALE_FACTOR) + .translationXBy(translateX) + .setDuration(FAB_SCALE_ANIMATION_DURATION); + } + + private void startFabScaleUpAnimation() { + final float iconWidth = mDraggableIconImage.getWidth(); + final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * + (mListSide == LIST_ON_LEFT_SIDE ? 1 : -1); + + mDraggableIconImage.animate() + .scaleX(1f) + .scaleY(1f) + .translationXBy(translateX) + .setDuration(FAB_SCALE_ANIMATION_DURATION); + } + + private AdapterView.OnItemClickListener mThemeClickedListener = + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + final String themePkgName = (String) view.getTag(R.id.tag_key_name); + final String appPkgName = Utils.getTopTaskPackageName(getContext()); + if (!TextUtils.isEmpty(appPkgName) && !TextUtils.isEmpty(themePkgName)) { + if (!Utils.themeHasOverlayForApp(getContext(), appPkgName, themePkgName)) { + Toast.makeText(getContext(), R.string.per_app_theme_app_not_overlaid_warning, + Toast.LENGTH_LONG).show(); + } + hideThemeList(true, new Runnable() { + @Override + public void run() { + ThemeManager tm = ThemeManager.getInstance(getContext()); + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + builder.setAppOverlay(appPkgName, themePkgName); + try { + tm.addClient(PerAppThemingWindow.this); + } catch (IllegalArgumentException e) { + /* ignore since this means we already have a listener added */ + } + tm.requestThemeChange(builder.build(), false); + } + }); + } else { + hideThemeList(); + } + } + }; + + private float calculateVelocityX() { + int depreciation = mDeltaXArray.size() + 1; + float sum = 0; + for (Float f : mDeltaXArray) { + depreciation--; + if (depreciation > MAX_DEPRECIATION){ + continue; + } + + sum += f / depreciation; + } + + return sum; + } + + private float calculateVelocityY() { + int depreciation = mDeltaYArray.size() + 1; + float sum = 0; + for (Float f : mDeltaYArray) { + depreciation--; + if (depreciation > 5) { + continue; + } + + sum += f / depreciation; + } + + return sum; + } + + // Timer for animation/automatic movement of the tray + private class AnimationTask { + // Ultimate destination coordinates toward which the view will move + int mDestX; + int mDestY; + long mDuration = 350; + long mStartTime; + float mTension = 1.4f; + Interpolator mInterpolator = new OvershootInterpolator(mTension); + long mSteps; + long mCurrentStep; + int mDistX; + int mOrigX; + int mDistY; + int mOrigY; + Handler mAnimationHandler = new Handler(); + OnAnimationFinishedListener mAnimationFinishedListener; + + public AnimationTask(int x, int y) { + setup(x, y); + } + + public AnimationTask() { + setup(calculateX(), calculateY()); + + float velocityX = calculateVelocityX(); + float velocityY = calculateVelocityY(); + mTension += Math.sqrt(velocityX * velocityX + velocityY * velocityY) / 200; + mInterpolator = new OvershootInterpolator(mTension); + } + + private void setup(int x, int y) { + if (mIsAnimationLocked) { + throw new RuntimeException("Returning to user's finger. Avoid animations while " + + "mIsAnimationLocked flag is set."); + } + + mDestX = x; + mDestY = y; + + mSteps = (int) (((float) mDuration) / 1000 * ANIMATION_FRAME_RATE); + mCurrentStep = 1; + mDistX = mParams.x - mDestX; + mOrigX = mParams.x; + mDistY = mParams.y - mDestY; + mOrigY = mParams.y; + } + + public long getDuration() { + return mDuration; + } + + public void setDuration(long duration) { + mDuration = duration; + setup(mDestX, mDestY); + } + + public OnAnimationFinishedListener getAnimationFinishedListener() { + return mAnimationFinishedListener; + } + + public void setAnimationFinishedListener(OnAnimationFinishedListener l) { + mAnimationFinishedListener = l; + } + + public Interpolator getInterpolator() { + return mInterpolator; + } + + public void setInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + private int calculateX() { + float velocityX = calculateVelocityX(); + int screenWidth = getScreenWidth(); + int destX = (mParams.x + mDraggableIcon.getWidth() / 2 > screenWidth / 2) + ? screenWidth - mDraggableIcon.getWidth() - MARGIN_HORIZONTAL + : 0 + MARGIN_HORIZONTAL; + + if (Math.abs(velocityX) > 50) { + destX = (velocityX > 0) ? screenWidth - mDraggableIcon.getWidth() + - MARGIN_HORIZONTAL : 0 + MARGIN_HORIZONTAL; + } + + return destX; + } + + private int calculateY() { + float velocityY = calculateVelocityY(); + mInterpolator = new OvershootInterpolator(mTension); + int screenHeight = getScreenHeight(); + int destY = mParams.y + (int) (velocityY * 3); + if (destY <= 0) { + destY = MARGIN_VERTICAL; + } + if (destY >= screenHeight - mDraggableIcon.getHeight()) { + destY = screenHeight - mDraggableIcon.getHeight() - MARGIN_VERTICAL; + } + + return destY; + } + + public void run() { + mStartTime = System.currentTimeMillis(); + for (mCurrentStep = 1; mCurrentStep <= mSteps; mCurrentStep++) { + long delay = mCurrentStep * mDuration / mSteps; + final float currentStep = mCurrentStep; + mAnimationHandler.postDelayed(new Runnable() { + @Override + public void run() { + // Update coordinates of the view + float percent = mInterpolator.getInterpolation(currentStep / mSteps); + updateIconPosition(mOrigX - (int) (percent * mDistX), mOrigY + - (int) (percent * mDistY)); + + // Notify the animation has ended + if (currentStep >= mSteps) { + if (mAnimationFinishedListener != null) mAnimationFinishedListener + .onAnimationFinished(); + } + } + }, delay); + } + } + + public void cancel() { + mAnimationHandler.removeCallbacksAndMessages(null); + mAnimationTask = null; + } + } + + /** + * We're extending BaseAdapter rather than CursorAdapter so that we can quickly re-order + * the list without needing to requery the provider. We're only storing the package name + * and theme title so there is minimum memory impact on doing this. + */ + class ThemesAdapter extends BaseAdapter { + private static final float HALF_OPACITY = 0.5f; + private static final float FULL_OPACITY = 1.0f; + + private ArrayList mThemes; + private LayoutInflater mInflater; + + public ThemesAdapter(Context context, Cursor cursor) { + mInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE); + mThemes = new ArrayList(cursor.getCount()); + populateThemes(cursor); + cursor.close(); + } + + @Override + public int getCount() { + return mThemes.size(); + } + + @Override + public Object getItem(int position) { + return mThemes.get(position).pkgName; + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.per_app_theme_list_item, parent, false); + Holder holder = new Holder(); + holder.title = (TextView) convertView.findViewById(R.id.theme_title); + holder.indicator = (TextView) convertView.findViewById(R.id.selected_indicator); + convertView.setTag(R.id.tag_key_holder, holder); + } + ThemeInfo themeInfo = mThemes.get(position); + Holder holder = (Holder) convertView.getTag(R.id.tag_key_holder); + holder.title.setText(themeInfo.title); + if (position == 0) { + holder.title.setAlpha(HALF_OPACITY); + holder.indicator.setVisibility(View.VISIBLE); + convertView.setEnabled(false); + } else { + holder.title.setAlpha(FULL_OPACITY); + holder.indicator.setVisibility(View.INVISIBLE); + convertView.setEnabled(true); + } + convertView.setTag(R.id.tag_key_name, themeInfo.pkgName); + return convertView; + } + + @Override + public boolean isEnabled(int position) { + return position != 0; + } + + public void setCurrentTheme(String pkgName) { + ThemeInfo info = null; + for (ThemeInfo ti : mThemes) { + if (ti.pkgName.equals(pkgName)) { + info = ti; + break; + } + } + if (info != null) { + Collections.sort(mThemes); + mThemes.remove(info); + mThemes.add(0, info); + notifyDataSetChanged(); + } + } + + private void populateThemes(Cursor cursor) { + mThemes.clear(); + while(cursor.moveToNext()) { + ThemeInfo info = new ThemeInfo( + cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)), + cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); + mThemes.add(info); + } + } + + private class Holder { + TextView title; + TextView indicator; + } + + private class ThemeInfo implements Comparable { + String pkgName; + String title; + + public ThemeInfo(String pkgName, String title) { + this.pkgName = pkgName; + this.title = title; + } + + @Override + public int compareTo(Object another) { + return this.title.compareTo(((ThemeInfo)another).title); + } + } + } +} diff --git a/src/org/cyanogenmod/theme/util/AudioUtils.java b/src/org/cyanogenmod/theme/util/AudioUtils.java new file mode 100644 index 0000000..c4ad68e --- /dev/null +++ b/src/org/cyanogenmod/theme/util/AudioUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ThemeConfig; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.util.Log; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.IOException; + +public class AudioUtils { + private static final String TAG = AudioUtils.class.getSimpleName(); + + public static void loadThemeAudible(Context context, int type, String pkgName, MediaPlayer mp) + throws PackageManager.NameNotFoundException { + if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) { + loadSystemAudible(type, mp); + return; + } + PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); + Context themeCtx = context.createPackageContext(pkgName, 0); + AssetManager assetManager = themeCtx.getAssets(); + String assetPath; + switch (type) { + case RingtoneManager.TYPE_ALARM: + assetPath = "alarms"; + break; + case RingtoneManager.TYPE_NOTIFICATION: + assetPath = "notifications"; + break; + case RingtoneManager.TYPE_RINGTONE: + assetPath = "ringtones"; + break; + default: + assetPath = null; + break; + } + if (assetPath != null) { + try { + String[] assetList = assetManager.list(assetPath); + if (assetList != null && assetList.length > 0) { + AssetFileDescriptor afd = assetManager.openFd(assetPath + + File.separator + assetList[0]); + if (mp != null) { + mp.reset(); + mp.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), afd.getLength()); + mp.prepare(); + } + } + } catch (IOException e) { + Log.e(TAG, "Unable to load sound for " + pkgName, e); + } + } + } + + public static void loadSystemAudible(int type, MediaPlayer mp) { + final String audiblePath = ThemeUtils.getDefaultAudiblePath(type); + if (audiblePath != null && (new File(audiblePath)).exists()) { + try { + mp.reset(); + mp.setDataSource(audiblePath); + mp.prepare(); + } catch (IOException e) { + Log.e(TAG, "Unable to load system sound " + audiblePath, e); + } + } + } + + public static Uri loadDefaultAudible(Context context, int type, MediaPlayer mp) + throws IOException { + Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + if (ringtoneUri != null) { + mp.reset(); + mp.setDataSource(context, ringtoneUri); + mp.prepare(); + } + + return ringtoneUri; + } +} diff --git a/src/org/cyanogenmod/theme/util/BootAnimationHelper.java b/src/org/cyanogenmod/theme/util/BootAnimationHelper.java new file mode 100644 index 0000000..1bd4ee7 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/BootAnimationHelper.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.ThemeConfig; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class BootAnimationHelper { + private static final String TAG = BootAnimationHelper.class.getSimpleName(); + private static final int MAX_REPEAT_COUNT = 3; + + public static final String THEME_INTERNAL_BOOT_ANI_PATH = + "assets/bootanimation/bootanimation.zip"; + public static final String SYSTEM_BOOT_ANI_PATH = "/system/media/bootanimation.zip"; + public static final String CACHED_SUFFIX = "_bootanimation.zip"; + + public static final int NUM_FIRST_LINE_PARAMETERS = 3; + public static final int NUM_PART_LINE_PARAMETERS = 4; + + public static class AnimationPart { + /** + * Number of times to play this part + */ + public int playCount; + /** + * If non-zero, pause for the given # of seconds before moving on to next part. + */ + public int pause; + /** + * The name of this part + */ + public String partName; + /** + * Time each frame is displayed + */ + public int frameRateMillis; + /** + * List of file names for the given frames in this part + */ + public List frames; + /** + * width of the animation + */ + public int width; + /** + * height of the animation + */ + public int height; + + public AnimationPart(int playCount, int pause, String partName, int frameRateMillis, + int width, int height) { + this.playCount = playCount == 0 ? MAX_REPEAT_COUNT : playCount; + this.pause = pause; + this.partName = partName; + this.frameRateMillis = frameRateMillis; + this.width = width; + this.height = height; + frames = new ArrayList(); + } + + public void addFrame(String frame) { + frames.add(frame); + } + } + + /** + * Gather up all the details for the given boot animation + * @param zip The bootanimation.zip + * @return A list of AnimationPart if successful, null if not. + * @throws IOException + */ + public static List parseAnimation(ZipFile zip) + throws IOException, BootAnimationException { + if (zip == null) { + // To make tracking down boot animation problems we'll throw a BootAnimationException + // instead of an IllegalArgumentException. + throw new BootAnimationException("Boot animation ZipFile cannot be null"); + } + List animationParts = null; + + ZipEntry ze = zip.getEntry("desc.txt"); + if (ze != null) { + animationParts = parseDescription(zip.getInputStream(ze)); + } else { + throw new BootAnimationException("Missing desc.txt in root of bootanimation.zip"); + } + + if (animationParts == null) { + // We really should not end up here but in case we do here's an exception for ya! + throw new BootAnimationException("Unable to load boot animation."); + } + + Iterator iterator = animationParts.iterator(); + while(iterator.hasNext()) { + AnimationPart a = iterator.next(); + for (Enumeration e = zip.entries();e.hasMoreElements();) { + ze = e.nextElement(); + if (!ze.isDirectory() && ze.getName().contains(a.partName)) { + a.addFrame(ze.getName()); + } + } + if (a.frames.size() > 0) { + Collections.sort(a.frames); + } else { + // This boot animation may be salvageable if there are still some other parts + // that are good. We'll remove this part and if there are no parts left by + // the time we have iterated over all the parts then we can throw an exception. + Log.w(TAG, String.format("No frames in part %s, removing from animation", + a.partName)); + iterator.remove(); + } + } + if (animationParts.size() == 0) { + throw new BootAnimationException("Boot animation must have at least one part."); + } + + return animationParts; + } + + /** + * Parses the desc.txt of the boot animation + * @param in InputStream to the desc.txt + * @return A list of the parts as given in desc.txt + * @throws IOException + */ + private static List parseDescription(InputStream in) + throws IOException, BootAnimationException { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + // read in suggested width, height, and frame rate from first line + String line = reader.readLine(); + String[] details = line.split(" "); + if (details.length != NUM_FIRST_LINE_PARAMETERS) { + throw new BootAnimationException(String.format( + "Invalid # of parameters on first line of desc.txt; exptected %d, read %d " + + "(\"%s\")", + NUM_FIRST_LINE_PARAMETERS, details.length, line)); + } + + // The items should be in the following order: width, height, frame rate + final int width = Integer.parseInt(details[0]); + final int height = Integer.parseInt(details[1]); + final int frameRateMillis = 1000 / Integer.parseInt(details[2]); + + List animationParts = new ArrayList(); + while ((line = reader.readLine()) != null) { + // trim off any leading and trailing spaces + line = line.trim(); + // if the line is empty continue on to the next + if (TextUtils.isEmpty(line)) continue; + + String[] info = line. split(" "); + if (info.length != NUM_PART_LINE_PARAMETERS) { + Log.w(TAG, String.format( + "Invalid # of part parameters; exptected %d, read %d (\"%s\")", + NUM_PART_LINE_PARAMETERS, info.length, line)); + // let's continue in case there are parts that are valid + continue; + } + if (!info[0].equals("p") && !info[0].equals("c")) { + Log.w(TAG, String.format( + "Unknown part type; expected 'p' or 'c', read %s (\"%s\")", info[0], line)); + + // let's continue in case there are parts that are valid + continue; + } + int playCount = Integer.parseInt(info[1]); + int pause = Integer.parseInt(info[2]); + String name = info[3]; + AnimationPart ap = new AnimationPart(playCount, pause, name, frameRateMillis, + width, height); + animationParts.add(ap); + } + + return animationParts; + } + + public static String getPreviewFrameEntryName(InputStream is) throws IOException { + ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is + : new ZipInputStream(new BufferedInputStream(is)); + ZipEntry ze; + // First thing to do is iterate over all the entries and the zip and store them + // for building the animations afterwards + String previewName = null; + while ((ze = zis.getNextEntry()) != null) { + final String entryName = ze.getName(); + if (entryName.contains("/") + && (entryName.endsWith(".png") || entryName.endsWith(".jpg"))) { + previewName = entryName; + } + } + + return previewName; + } + + public static Bitmap loadPreviewFrame(Context context, InputStream is, String previewName) + throws IOException { + ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is + : new ZipInputStream(new BufferedInputStream(is)); + ZipEntry ze; + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = am.isLowRamDevice() ? 4 : 2; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + // First thing to do is iterate over all the entries and the zip and store them + // for building the animations afterwards + Bitmap preview = null; + while ((ze = zis.getNextEntry()) != null && preview == null) { + final String entryName = ze.getName(); + if (entryName.equals(previewName)) { + preview = BitmapFactory.decodeStream(zis, null, opts); + } + } + zis.close(); + + return preview; + } + + public static void clearBootAnimationCache(Context context) { + File cache = context.getCacheDir(); + if (cache.exists()) { + for(File f : cache.listFiles()) { + // volley stores stuff in cache so don't delete the volley directory + if(!f.isDirectory() && f.getName().endsWith(CACHED_SUFFIX)) f.delete(); + } + } + } + + public static class LoadBootAnimationImage extends AsyncTask { + private ImageView imv; + private String path; + private Context context; + + public LoadBootAnimationImage(ImageView imv, Context context, String path) { + this.imv = imv; + this.context = context; + this.path = path; + } + + @Override + protected Bitmap doInBackground(Object... params) { + Bitmap bitmap = null; + String previewName = null; + // this is ugly, ugly, ugly. Did I mention this is ugly? + try { + if (ThemeConfig.SYSTEM_DEFAULT.equals(path)) { + previewName = getPreviewFrameEntryName( + new FileInputStream(SYSTEM_BOOT_ANI_PATH)); + bitmap = loadPreviewFrame( + context, new FileInputStream(SYSTEM_BOOT_ANI_PATH), previewName); + } else { + final Context themeCtx = context.createPackageContext(path, 0); + previewName = getPreviewFrameEntryName( + themeCtx.getAssets().open("bootanimation/bootanimation.zip")); + bitmap = loadPreviewFrame(context, + themeCtx.getAssets().open("bootanimation/bootanimation.zip"), + previewName); + } + } catch (Exception e) { + // don't care since a null bitmap will be returned + e.printStackTrace(); + } + return bitmap; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null && imv != null) { + imv.setVisibility(View.VISIBLE); + imv.setImageBitmap(result); + } + } + } + + public static class BootAnimationException extends Exception { + public BootAnimationException(String detailMessage) { + super(detailMessage); + } + } +} diff --git a/src/org/cyanogenmod/theme/util/CursorLoaderHelper.java b/src/org/cyanogenmod/theme/util/CursorLoaderHelper.java new file mode 100644 index 0000000..7fcd786 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/CursorLoaderHelper.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; + +import cyanogenmod.app.ThemeVersion; +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; + +public class CursorLoaderHelper { + + public static final int LOADER_ID_INVALID = -1; + public static final int LOADER_ID_ALL = 0; + public static final int LOADER_ID_STATUS_BAR = 1; + public static final int LOADER_ID_FONT = 2; + public static final int LOADER_ID_ICONS = 3; + public static final int LOADER_ID_WALLPAPER = 4; + public static final int LOADER_ID_NAVIGATION_BAR = 5; + public static final int LOADER_ID_LOCKSCREEN = 6; + public static final int LOADER_ID_STYLE = 7; + public static final int LOADER_ID_BOOT_ANIMATION = 8; + public static final int LOADER_ID_RINGTONE = 9; + public static final int LOADER_ID_NOTIFICATION = 10; + public static final int LOADER_ID_ALARM = 11; + public static final int LOADER_ID_LIVE_LOCK_SCREEN = 12; + public static final int LOADER_ID_INSTALLED_THEMES = 1000; + public static final int LOADER_ID_APPLIED = 1001; + + private static final long DEFAULT_COMPONENT_ID = 0; + + private static int mThemeVersion = ThemeVersion.getVersion(); + + public static Loader chooserActivityCursorLoader(Context context, int id, + String appliedBaseTheme) { + String selection = null; + String selectionArgs[] = null; + String sortOrder = null; + String[] projection = null; + Uri contentUri = null; + + switch (id) { + case LOADER_ID_INSTALLED_THEMES: + selection = ThemesColumns.PRESENT_AS_THEME + "=? AND " + + ThemesColumns.INSTALL_STATE + "=?"; + selectionArgs = new String[] { "1", "" + ThemesColumns.InstallState.INSTALLED}; + // sort in ascending order but make sure the "default" theme is always first + sortOrder = "(" + ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " + + "(" + ThemesColumns.PKG_NAME + "='" + appliedBaseTheme + "') DESC, " + + ThemesColumns.INSTALL_TIME + " DESC"; + contentUri = ThemesColumns.CONTENT_URI; + projection = new String[] {ThemesColumns.PKG_NAME, ThemesColumns.TITLE, + ThemesColumns.AUTHOR}; + break; + case LOADER_ID_APPLIED: + //TODO: Mix n match query should only be done once + contentUri = ThemesContract.MixnMatchColumns.CONTENT_URI; + selection = null; + selectionArgs = null; + break; + } + + return new CursorLoader(context, contentUri, projection, selection, + selectionArgs, sortOrder); + } + + public static Loader componentSelectorCursorLoader(Context context, int id) { + Uri uri = PreviewColumns.CONTENT_URI; + String selection; + String[] selectionArgs = { "1" }; + String[] projection = { ThemesColumns.TITLE, ThemesColumns.PKG_NAME }; + switch(id) { + case LOADER_ID_STATUS_BAR: + selection = MODIFIES_STATUS_BAR + "=?"; + projection = new String[] { + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_NAVIGATION_BAR: + selection = MODIFIES_NAVIGATION_BAR + "=?"; + projection = new String[] { + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.STATUSBAR_BACKGROUND, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + }; + break; + case LOADER_ID_FONT: + // fonts don't have generated previews so use the ThemesColumns.CONTENT_URI + uri = ThemesColumns.CONTENT_URI; + selection = MODIFIES_FONTS + "=?"; + break; + case LOADER_ID_ICONS: + selection = MODIFIES_ICONS + "=?"; + projection = new String[] { + PreviewColumns.ICON_PREVIEW_1, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_STYLE: + selection = MODIFIES_OVERLAYS + "=?"; + projection = new String[] { + PreviewColumns.STYLE_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_WALLPAPER: + selection = MODIFIES_LAUNCHER + "=?"; + if (mThemeVersion >= 3) { + uri = PreviewColumns.COMPONENTS_URI; + projection = new String[]{ + PreviewColumns.WALLPAPER_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + PreviewColumns.WALLPAPER_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + } + break; + case LOADER_ID_BOOT_ANIMATION: + selection = MODIFIES_BOOT_ANIM + "=?"; + projection = new String[] { + PreviewColumns.BOOTANIMATION_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_RINGTONE: + selection = MODIFIES_RINGTONES + "=?"; + break; + case LOADER_ID_NOTIFICATION: + selection = MODIFIES_NOTIFICATIONS + "=?"; + break; + case LOADER_ID_ALARM: + selection = MODIFIES_ALARMS + "=?"; + break; + case LOADER_ID_LOCKSCREEN: + selection = MODIFIES_LOCKSCREEN + "=?"; + selectionArgs = new String[] { "1" }; + if (mThemeVersion >= 3) { + projection = new String[]{ + PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + ThemesColumns.PKG_NAME + }; + } + break; + case LOADER_ID_LIVE_LOCK_SCREEN: + selection = MODIFIES_LIVE_LOCK_SCREEN + "=?"; + selectionArgs = new String[] { "1" }; + if (mThemeVersion >= 3) { + projection = new String[]{ + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + ThemesColumns.PKG_NAME + }; + } + break; + default: + return null; + } + // sort in ascending order but make sure the "default" theme is always first + String sortOrder = "(" + ThemesContract.ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " + + ThemesContract.ThemesColumns.TITLE + " ASC"; + return new CursorLoader(context, uri, projection, selection, selectionArgs, sortOrder); + } + + public static Loader myThemeFragmentCursorLoader(Context context, int id) { + Uri uri; + String[] projection; + projection = new String[]{ + PreviewColumns.WALLPAPER_PREVIEW, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.NAVBAR_HOME_BUTTON, + PreviewColumns.NAVBAR_RECENT_BUTTON, + PreviewColumns.ICON_PREVIEW_1, + PreviewColumns.ICON_PREVIEW_2, + PreviewColumns.ICON_PREVIEW_3, + PreviewColumns.LOCK_WALLPAPER_PREVIEW, + PreviewColumns.STYLE_PREVIEW, + PreviewColumns.NAVBAR_BACKGROUND, + PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW + }; + uri = PreviewColumns.APPLIED_URI; + return new CursorLoader(context, uri, projection, null, null, null); + } + + public static Loader themeFragmentCursorLoader(Context context, int id, String pkgName, + long componentId) { + Uri uri = PreviewColumns.CONTENT_URI; + String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?"; + String[] selectionArgs = new String[] { pkgName }; + String[] projection = null; + switch (id) { + case LOADER_ID_ALL: + if (mThemeVersion >= 3) { + // Load all default component previews (component_id == 0) + selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; + selectionArgs = new String[] { pkgName, String.valueOf(DEFAULT_COMPONENT_ID) }; + } else { + // SQL query will fail if we ask for PreviewColumns.COMPONENT_ID, don't add it. + selectionArgs = new String[]{pkgName}; + } + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + ThemesColumns.AUTHOR, + ThemesColumns.WALLPAPER_URI, + ThemesColumns.HOMESCREEN_URI, + ThemesColumns.TARGET_API, + // Theme abilities + ThemesColumns.MODIFIES_LAUNCHER, + ThemesColumns.MODIFIES_LOCKSCREEN, + ThemesColumns.MODIFIES_ALARMS, + ThemesColumns.MODIFIES_BOOT_ANIM, + ThemesColumns.MODIFIES_FONTS, + ThemesColumns.MODIFIES_ICONS, + ThemesColumns.MODIFIES_NAVIGATION_BAR, + ThemesColumns.MODIFIES_OVERLAYS, + ThemesColumns.MODIFIES_RINGTONES, + ThemesColumns.MODIFIES_STATUS_BAR, + ThemesColumns.MODIFIES_NOTIFICATIONS, + //Previews + PreviewColumns.WALLPAPER_PREVIEW, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.NAVBAR_HOME_BUTTON, + PreviewColumns.NAVBAR_RECENT_BUTTON, + PreviewColumns.ICON_PREVIEW_1, + PreviewColumns.ICON_PREVIEW_2, + PreviewColumns.ICON_PREVIEW_3, + PreviewColumns.LOCK_WALLPAPER_PREVIEW, + PreviewColumns.STYLE_PREVIEW, + PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW + }; + break; + case LOADER_ID_STATUS_BAR: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT + }; + break; + case LOADER_ID_FONT: + uri = ThemesColumns.CONTENT_URI; + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE + }; + break; + case LOADER_ID_ICONS: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.ICON_PREVIEW_1, + PreviewColumns.ICON_PREVIEW_2, + PreviewColumns.ICON_PREVIEW_3, + }; + break; + case LOADER_ID_WALLPAPER: + if (mThemeVersion >= 3) { + uri = PreviewColumns.COMPONENTS_URI; + // Load specified wallpaper previews (component_id is specified) + selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; + selectionArgs = new String[]{pkgName, String.valueOf(componentId)}; + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.WALLPAPER_PREVIEW, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.WALLPAPER_PREVIEW + }; + } + break; + case LOADER_ID_NAVIGATION_BAR: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.NAVBAR_HOME_BUTTON, + PreviewColumns.NAVBAR_RECENT_BUTTON + }; + break; + case LOADER_ID_LOCKSCREEN: + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.LOCK_WALLPAPER_PREVIEW, + }; + break; + case LOADER_ID_LIVE_LOCK_SCREEN: + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW + }; + break; + case LOADER_ID_STYLE: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.STYLE_PREVIEW + }; + break; + case LOADER_ID_BOOT_ANIMATION: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE + }; + break; + case LOADER_ID_RINGTONE: + case LOADER_ID_NOTIFICATION: + case LOADER_ID_ALARM: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE + }; + break; + } + return new CursorLoader(context, uri, projection, selection, selectionArgs, null); + } + + public static Object[] getRowFromCursor(Cursor cursor) { + Object[] row = null; + if (cursor != null) { + int colCount = cursor.getColumnCount(); + row = new Object[colCount]; + for (int indx = 0; indx < colCount; indx++) { + row[indx] = getFieldValueFromRow(cursor, indx); + } + } + return row; + } + + public static Object getFieldValueFromRow(Cursor cursor, int position) { + switch (cursor.getType(position)) { + case Cursor.FIELD_TYPE_BLOB: return cursor.getBlob(position); + case Cursor.FIELD_TYPE_FLOAT: return cursor.getFloat(position); + case Cursor.FIELD_TYPE_INTEGER: return cursor.getInt(position); + case Cursor.FIELD_TYPE_STRING: return cursor.getString(position); + case Cursor.FIELD_TYPE_NULL: + default: + return null; + } + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/util/FontConfigParser.java b/src/org/cyanogenmod/theme/util/FontConfigParser.java new file mode 100644 index 0000000..a5b4de9 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/FontConfigParser.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Parses an XML font config. Example: + * + * + * + * + * + * sans-serif + * arial + * + * + * Roboto-Regular.ttf + * Roboto-Bold.ttf + * Roboto-Italic.ttf + * Roboto-BoldItalic.ttf + * + * + * + * ... + * + * + */ +public class FontConfigParser { + + public static class Family { + public List nameset = new ArrayList(); + public List fileset = new ArrayList(); + } + + public static List parse(InputStream in) throws XmlPullParserException, IOException { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parser.nextTag(); + return readFamilySet(parser); + } finally { + in.close(); + } + } + + private static List readFamilySet(XmlPullParser parser) throws XmlPullParserException, IOException { + List families = new ArrayList(); + parser.require(XmlPullParser.START_TAG, null, "familyset"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + + // Starts by looking for the entry tag + if (name.equals("family")) { + Family family = readFamily(parser); + families.add(family); + } + } + return families; + } + + private static Family readFamily(XmlPullParser parser) throws XmlPullParserException, IOException { + Family family = new Family(); + parser.require(XmlPullParser.START_TAG, null, "family"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("nameset")) { + List nameset = readNameset(parser); + family.nameset = nameset; + } else if (name.equals("fileset")) { + List fileset = readFileset(parser); + family.fileset = fileset; + } else { + skip(parser); + } + } + return family; + } + + private static List readNameset(XmlPullParser parser) throws XmlPullParserException, IOException { + List names = new ArrayList(); + parser.require(XmlPullParser.START_TAG, null, "nameset"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String tagname = parser.getName(); + if (tagname.equals("name")) { + String name = readText(parser); + names.add(name); + } else { + skip(parser); + } + } + return names; + } + + private static List readFileset(XmlPullParser parser) throws XmlPullParserException, IOException { + List files = new ArrayList(); + parser.require(XmlPullParser.START_TAG, null, "fileset"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("file")) { + String file = readText(parser); + files.add(file); + } else { + skip(parser); + } + } + return files; + } + + // For the tags title and summary, extracts their text values. + private static String readText(XmlPullParser parser) throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } +} diff --git a/src/org/cyanogenmod/theme/util/IconPreviewHelper.java b/src/org/cyanogenmod/theme/util/IconPreviewHelper.java new file mode 100644 index 0000000..3f5b897 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/IconPreviewHelper.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.app.ComposedIconInfo; +import android.app.IconPackHelper; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; + +/** + * This class handles all the logic to build a preview icon + * If the system currently has a theme applied we do NOT + * want this code to be impacted by it. So code in this + * class creates special "no theme attached" resource objects + * to retrieve objects from. + */ +public class IconPreviewHelper { + private static final String TAG = IconPreviewHelper.class.getSimpleName(); + private final static float ICON_SCALE_FACTOR = 1.3f; //Arbitrary. Looks good + + private Context mContext; + private DisplayMetrics mDisplayMetrics; + private Configuration mConfiguration; + private int mIconDpi = 0; + private String mThemePkgName; + private IconPackHelper mIconPackHelper; + private int mIconSize; + + /** + * @param themePkgName - The package name of the theme we wish to preview + */ + public IconPreviewHelper(Context context, String themePkgName) { + mContext = context; + mDisplayMetrics = context.getResources().getDisplayMetrics(); + mConfiguration = context.getResources().getConfiguration(); + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + mIconDpi = (int) (am.getLauncherLargeIconDensity() * ICON_SCALE_FACTOR); + mThemePkgName = themePkgName; + mIconPackHelper = new IconPackHelper(mContext); + try { + mIconPackHelper.loadIconPack(mThemePkgName); + } catch (NameNotFoundException e) {} + mIconSize = (int) (am.getLauncherLargeIconSize() * ICON_SCALE_FACTOR); + } + + /** + * Returns the actual label name for a given component + * If the activity does not have a label it will return app's label + * If neither has a label returns empty string + */ + public String getLabel(ComponentName component) { + String label = ""; + try { + PackageManager pm = mContext.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(component.getPackageName(), 0); + ActivityInfo activityInfo = pm.getActivityInfo(component, 0); + + AssetManager assets = new AssetManager(); + assets.addAssetPath(appInfo.publicSourceDir); + Resources res = new Resources(assets, mDisplayMetrics, mConfiguration); + + if (activityInfo.labelRes != 0) { + label = res.getString(activityInfo.labelRes); + } else if (appInfo.labelRes != 0) { + label = res.getString(appInfo.labelRes); + } + } catch(NameNotFoundException exception) { + Log.e(TAG, "unable to find pkg for " + component.toString()); + } + return label; + } + + /** + * Returns the icon for the given component regardless of the system's + * currently applied theme. If the preview theme does not support the icon, then + * return the system default icon. + */ + public Drawable getIcon(ComponentName component) { + String packageName = component.getPackageName(); + String activityName = component.getClassName(); + Drawable icon = getThemedIcon(packageName, activityName); + if (icon == null) { + icon = getDefaultIcon(packageName, activityName); + } + if (icon != null) { + icon.setBounds(0, 0, mIconSize, mIconSize); + } + return icon; + } + + private Drawable getThemedIcon(String pkgName, String activityName) { + Drawable drawable = null; + ActivityInfo info = new ActivityInfo(); + info.packageName = pkgName; + info.name = activityName; + drawable = mIconPackHelper.getDrawableForActivityWithDensity(info, mIconDpi); + + return drawable; + } + + /** + * Returns the default icon. This can be the normal icon associated with the app or a composed + * icon if the icon pack supports background, mask, and/or foreground. + * @param pkgName + * @param activityName + * @return + */ + public Drawable getDefaultIcon(String pkgName, String activityName) { + Drawable drawable = null; + ComponentName component = new ComponentName(pkgName, activityName); + PackageManager pm = mContext.getPackageManager(); + Resources res = null; + try { + ActivityInfo info = pm.getActivityInfo(component, 0); + ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, 0); + + AssetManager assets = new AssetManager(); + assets.addAssetPath(appInfo.publicSourceDir); + res = new Resources(assets, mDisplayMetrics, mConfiguration); + + final int iconId = info.icon != 0 ? info.icon : appInfo.icon; + info.themedIcon = 0; + setupComposedIcon(res, info, iconId); + drawable = getFullResIcon(res, iconId); + } catch (NameNotFoundException e2) { + Log.w(TAG, "Unable to get the icon for " + pkgName + " using default"); + } + drawable = (drawable != null) ? + getComposedIcon(res, drawable) : getFullResDefaultActivityIcon(); + return drawable; + } + + private Drawable getComposedIcon(Resources res, Drawable baseIcon) { + ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); + if (res != null && iconInfo != null && (iconInfo.iconBacks != null || + iconInfo.iconMask != 0 || iconInfo.iconUpon != 0)) { + return IconPackHelper.IconCustomizer.getComposedIconDrawable(baseIcon, res, iconInfo); + } + return baseIcon; + } + + private void setupComposedIcon(Resources res, ActivityInfo info, int iconId) { + ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); + if (iconInfo.iconBacks == null && iconInfo.iconMask == 0 && iconInfo.iconUpon == 0) { + return; + } + + res.setComposedIconInfo(iconInfo); + + SparseArray icons = new SparseArray(1); + info.themedIcon = 0; + icons.put(iconId, info); + res.setIconResources(icons); + } + + private Drawable getFullResIcon(Resources resources, int iconId) { + Drawable d; + try { + d = resources.getDrawableForDensity(iconId, mIconDpi, null, false); + } catch (Resources.NotFoundException e) { + d = null; + } + return (d != null) ? d : getFullResDefaultActivityIcon(); + } + + private Drawable getFullResDefaultActivityIcon() { + return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); + } +} diff --git a/src/org/cyanogenmod/theme/util/NotificationHelper.java b/src/org/cyanogenmod/theme/util/NotificationHelper.java new file mode 100644 index 0000000..ebbdb66 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/NotificationHelper.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.BitmapFactory; +import android.text.TextUtils; + +import org.cyanogenmod.theme.chooser.ChooserActivity; +import org.cyanogenmod.theme.chooser.R; + +public class NotificationHelper { + private static final int NOTIFICATION_ID = 0x434D5443; + + public static void postThemeInstalledNotification(Context context, String pkgName) { + String themeName = null; + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); + if (pi.themeInfo != null) { + themeName = pi.themeInfo.name; + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return; + } + if (TextUtils.isEmpty(themeName)) { + return; + } + + int themeCount = PreferenceUtils.getNewlyInstalledThemeCount(context) + 1; + + Intent intent = new Intent(context, ChooserActivity.class); + intent.setAction(Intent.ACTION_MAIN); + intent.putExtra("pkgName", pkgName); + PendingIntent pi = PendingIntent.getActivity(context, 0, intent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); + + String title = null; + String content = null; + final Resources res = context.getResources(); + if (themeCount == 1) { + title = String.format(res.getString( + R.string.theme_installed_notification_title), themeName); + content = res.getString(R.string.theme_installed_notification_text); + } else { + title = String.format(res.getString(R.string.themes_installed_notification_title), + themeCount); + content = String.format(res.getQuantityString( + R.plurals.themes_installed_notification_text, themeCount -1), + themeName, themeCount - 1); + } + NotificationManager nm = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + Notification notice = new Notification.Builder(context) + .setAutoCancel(true) + .setOngoing(false) + .setContentTitle(title) + .setContentText(content) + .setContentIntent(pi) + .setSmallIcon(R.drawable.ic_notify) + .setWhen(System.currentTimeMillis()) + .build(); + if (themeCount > 1) notice.number = themeCount; + nm.notify(NOTIFICATION_ID, notice); + PreferenceUtils.setNewlyInstalledThemeCount(context, themeCount); + } + + public static void cancelNotifications(Context context) { + NotificationManager nm = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel(NOTIFICATION_ID); + PreferenceUtils.setNewlyInstalledThemeCount(context, 0); + } +} diff --git a/src/org/cyanogenmod/theme/util/PreferenceUtils.java b/src/org/cyanogenmod/theme/util/PreferenceUtils.java new file mode 100644 index 0000000..080615f --- /dev/null +++ b/src/org/cyanogenmod/theme/util/PreferenceUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.ThemeUtils; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.text.TextUtils; +import android.util.Log; + +import java.util.HashSet; +import java.util.Set; + +public class PreferenceUtils { + private static final String TAG = PreferenceUtils.class.getSimpleName(); + + public static final String PREF_APPLIED_BASE_THEME = "applied_base_theme"; + public static final String PREF_UPDATED_THEMES = "updated_themes"; + public static final String PREF_NEWLY_INSTALLED_THEME_COUNT = "newly_installed_theme_count"; + public static final String PREF_INSTALLED_THEMES_PROCESSING = "installed_themes_processing"; + public static final String PREF_SHOW_PER_APP_THEMING_NEW_TAG = "show_per_app_new_tag"; + + public static SharedPreferences getSharedPreferences(Context context) { + if (context == null) return null; + return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); + } + + public static String getAppliedBaseTheme(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs == null) return null; + + final Resources res = context.getResources(); + final ThemeConfig config = res.getConfiguration().themeConfig; + String appliedTheme = config != null + ? config.getOverlayPkgName() + : ThemeConfig.SYSTEM_DEFAULT; + return prefs.getString(PREF_APPLIED_BASE_THEME, appliedTheme); + } + + public static void setAppliedBaseTheme(Context context, String pkgName) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + prefs.edit().putString(PREF_APPLIED_BASE_THEME, pkgName).apply(); + } + } + + public static Set getUpdatedThemes(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs == null) return null; + + return prefs.getStringSet(PREF_UPDATED_THEMES, null); + } + + public static void addUpdatedTheme(Context context, String pkgName) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + Set updatedThemes = new HashSet(); + Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); + if (current != null) { + updatedThemes.addAll(current); + } + if (updatedThemes.add(pkgName)) { + prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); + } + } + } + + public static void removeUpdatedTheme(Context context, String pkgName) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + Set updatedThemes = new HashSet(); + Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); + if (current != null) { + updatedThemes.addAll(current); + } + if (updatedThemes.remove(pkgName)) { + prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); + } + } + } + + public static boolean hasThemeBeenUpdated(Context context, String pkgName) { + Set updatedThemes = getUpdatedThemes(context); + return updatedThemes != null && updatedThemes.contains(pkgName); + } + + public static int getNewlyInstalledThemeCount(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs == null) return 0; + + return prefs.getInt(PREF_NEWLY_INSTALLED_THEME_COUNT, 0); + } + + public static void setNewlyInstalledThemeCount(Context context, int count) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + prefs.edit().putInt(PREF_NEWLY_INSTALLED_THEME_COUNT, count).apply(); + } + } + + public static boolean getShowPerAppThemeNewTag(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + return prefs.getBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, true); + } + + return false; + } + + public static void setShowPerAppThemeNewTag(Context context, boolean show) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + prefs.edit().putBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, show).apply(); + } + } +} diff --git a/src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java b/src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java new file mode 100644 index 0000000..cf5ddfc --- /dev/null +++ b/src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.FontListParser; +import android.graphics.FontListParser.Family; +import android.graphics.Typeface; +import android.util.Log; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.List; + +/** + * Assists in loading a themes font typefaces. + * Will load system default if there is a load issue + */ +public class ThemedTypefaceHelper { + private static final String TAG = ThemedTypefaceHelper.class.getName(); + private static final String FAMILY_SANS_SERIF = "sans-serif"; + private static final String FONTS_DIR = "fonts"; + private static final String SYSTEM_FONTS_XML = "/system/etc/system_fonts.xml"; + private static final String SYSTEM_FONTS_DIR = "/system/fonts"; + + private boolean mIsLoaded; + private Context mThemeContext; + private List mFamilies; + private Typeface[] mTypefaces = new Typeface[4]; + + public void load(Context context, String pkgName) { + try { + loadThemedFonts(context, pkgName); + return; + } catch(Exception e) { + Log.w(TAG, "Unable to parse and load themed fonts for " + pkgName + + ". Falling back to system fonts", e ); + } + + try { + loadSystemFonts(); + return; + } catch(Exception e) { + Log.e(TAG, "Parsing system fonts failed. Falling back to Typeface loaded fonts", e); + } + + // There is no reason for this to happen unless someone + // messed up the system_fonts.xml + loadDefaultFonts(); + } + + private void loadThemedFonts(Context context, String pkgName) throws Exception { + //Parse the font XML + mThemeContext = context.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY); + AssetManager assetManager = mThemeContext.getAssets(); + InputStream is = assetManager.open(FONTS_DIR + File.separator + ThemeUtils.FONT_XML); + FontListParser.Config fontConfig = FontListParser.parse(is, FONTS_DIR); + mFamilies = fontConfig.families; + + //Load the typefaces for sans-serif + Family sanSerif = getFamily(FAMILY_SANS_SERIF); + mTypefaces[Typeface.NORMAL] = loadTypeface(sanSerif, Typeface.NORMAL); + mTypefaces[Typeface.BOLD] = loadTypeface(sanSerif, Typeface.BOLD); + mTypefaces[Typeface.ITALIC] = loadTypeface(sanSerif, Typeface.ITALIC); + mTypefaces[Typeface.BOLD_ITALIC] = loadTypeface(sanSerif, Typeface.BOLD_ITALIC); + mIsLoaded = true; + } + + private void loadSystemFonts() throws Exception { + //Parse the system font XML + File file = new File(SYSTEM_FONTS_XML); + InputStream is = new FileInputStream(file); + FontListParser.Config fontConfig = FontListParser.parse(is, SYSTEM_FONTS_DIR); + mFamilies = fontConfig.families; + + //Load the typefaces for sans-serif + Family sanSerif = getFamily(FAMILY_SANS_SERIF); + if (mTypefaces[Typeface.NORMAL] == null) { + mTypefaces[Typeface.NORMAL] = loadSystemTypeface(sanSerif, Typeface.NORMAL); + } + if (mTypefaces[Typeface.BOLD] == null) { + mTypefaces[Typeface.BOLD] = loadSystemTypeface(sanSerif, Typeface.BOLD); + } + if (mTypefaces[Typeface.ITALIC] == null) { + mTypefaces[Typeface.ITALIC] = loadSystemTypeface(sanSerif, Typeface.ITALIC); + } + if (mTypefaces[Typeface.BOLD_ITALIC] == null) { + mTypefaces[Typeface.BOLD_ITALIC] = loadSystemTypeface(sanSerif, Typeface.BOLD_ITALIC); + } + mIsLoaded = true; + } + + private void loadDefaultFonts() { + mTypefaces[Typeface.NORMAL] = Typeface.DEFAULT; + mTypefaces[Typeface.BOLD] = Typeface.DEFAULT_BOLD; + mIsLoaded = true; + } + + private Family getFamily(String familyName) throws Exception { + for(Family family : mFamilies) { + if (family.name.equals(familyName)) { + return family; + } + } + throw new Exception("Unable to find " + familyName); + } + + private Typeface loadTypeface(Family family, int style) { + AssetManager assets = mThemeContext.getAssets(); + String path = family.fonts.get(style).fontName; + return Typeface.createFromAsset(assets, path); + } + + private Typeface loadSystemTypeface(Family family, int style) { + return Typeface.createFromFile(family.fonts.get(style).fontName); + } + + public Typeface getTypeface(int style) { + if (!mIsLoaded) throw new IllegalStateException("Helper was not loaded"); + return mTypefaces[style]; + } +} diff --git a/src/org/cyanogenmod/theme/util/TypefaceHelperCache.java b/src/org/cyanogenmod/theme/util/TypefaceHelperCache.java new file mode 100644 index 0000000..76183f8 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/TypefaceHelperCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.content.Context; + +import java.util.HashMap; +import java.util.Map; + +public class TypefaceHelperCache { + private static TypefaceHelperCache sHelperCache; + private final Map mCache; + + private TypefaceHelperCache() { + mCache = new HashMap(); + } + + public static synchronized TypefaceHelperCache getInstance() { + if (sHelperCache == null) { + sHelperCache = new TypefaceHelperCache(); + } + return sHelperCache; + } + + public ThemedTypefaceHelper getHelperForTheme(Context context, String pkgName) { + synchronized (mCache) { + ThemedTypefaceHelper helper = mCache.get(pkgName); + if (helper == null) { + helper = new ThemedTypefaceHelper(); + helper.load(context, pkgName); + mCache.put(pkgName, helper); + } + return helper; + } + } + + public int getTypefaceCount() { + synchronized (mCache) { + return mCache.size(); + } + } +} diff --git a/src/org/cyanogenmod/theme/util/Utils.java b/src/org/cyanogenmod/theme/util/Utils.java new file mode 100644 index 0000000..94afecb --- /dev/null +++ b/src/org/cyanogenmod/theme/util/Utils.java @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.app.WallpaperManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.ResolveInfo; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.TypedValue; +import android.view.IWindowManager; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; + +import org.cyanogenmod.theme.chooser.ChooserActivity; + +import cyanogenmod.externalviews.KeyguardExternalView; +import cyanogenmod.providers.CMSettings; +import cyanogenmod.providers.ThemesContract; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.security.InvalidParameterException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static android.content.res.ThemeConfig.SYSTEM_DEFAULT; + +public class Utils { + private static final String TAG = Utils.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String OVERLAY_BASE_PATH = "overlays" + File.separator; + + public static Bitmap decodeFile(String path, int reqWidth, int reqHeight) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + + // Determine insample size + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, opts); + opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); + + // Decode the bitmap, regionally if necessary + Bitmap bitmap = null; + opts.inJustDecodeBounds = false; + Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); + try { + if (rect != null) { + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false); + // Check if we can downsample more now that we cropped + opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), + reqWidth, reqHeight); + bitmap = decoder.decodeRegion(rect, opts); + } else { + bitmap = BitmapFactory.decodeFile(path, opts); + } + } catch (IOException e) { + Log.e(TAG, "Unable to open resource in path" + path, e); + } + return bitmap; + } + + public static Bitmap decodeResource(Resources res, int resId, int reqWidth, int reqHeight) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + + // Determine insample size + opts.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, opts); + opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); + + // Decode the bitmap, regionally if necessary + Bitmap bitmap = null; + opts.inJustDecodeBounds = false; + Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); + + InputStream stream = null; + try { + if (rect != null) { + stream = res.openRawResource(resId, new TypedValue()); + if (stream == null) return null; + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(stream, false); + // Check if we can downsample a little more now that we cropped + opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), + reqWidth, reqHeight); + bitmap = decoder.decodeRegion(rect, opts); + } else { + bitmap = BitmapFactory.decodeResource(res, resId, opts); + } + } catch (IOException e) { + Log.e(TAG, "Unable to open resource " + resId, e); + } finally { + closeQuiet(stream); + } + return bitmap; + } + + + public static Bitmap getBitmapFromAsset(Context ctx, String path,int reqWidth, int reqHeight) { + if (ctx == null || path == null) + return null; + + String ASSET_BASE = "file:///android_asset/"; + path = path.substring(ASSET_BASE.length()); + + + Bitmap bitmap = null; + try { + AssetManager assets = ctx.getAssets(); + InputStream is = assets.open(path); + + // Determine insample size + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, null, opts); + opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); + is.close(); + + // Decode the bitmap, regionally if neccessary + is = assets.open(path); + opts.inJustDecodeBounds = false; + Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); + if (rect != null) { + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false); + // Check if we can downsample a little more now that we cropped + opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), + reqWidth, reqHeight); + bitmap = decoder.decodeRegion(rect, opts); + } else { + bitmap = BitmapFactory.decodeStream(is); + } + } catch (IOException e) { + e.printStackTrace(); + } + return bitmap; + } + + + /** + * For excessively large images with an awkward ratio we + * will want to crop them + * @return + */ + public static Rect getCropRectIfNecessary( + BitmapFactory.Options options,int reqWidth, int reqHeight) { + Rect rect = null; + // Determine downsampled size + int width = options.outWidth / options.inSampleSize; + int height = options.outHeight / options.inSampleSize; + + if ((reqHeight * 1.5 < height)) { + int bottom = height/ 4; + int top = bottom + height/2; + rect = new Rect(0, bottom, width, top); + } else if ((reqWidth * 1.5 < width)) { + int left = width / 4; + int right = left + height/2; + rect = new Rect(left, 0, right, height); + } + return rect; + } + + public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); + } + + // Modified from original source: + // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html + public static int calculateInSampleSize( + int decodeWidth, int decodeHeight, int reqWidth, int reqHeight) { + // Raw height and width of image + int inSampleSize = 1; + + if (decodeHeight > reqHeight || decodeWidth > reqWidth) { + final int halfHeight = decodeHeight / 2; + final int halfWidth = decodeWidth / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight && + (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public static InputStream getInputStreamFromAsset( + Context ctx, String path) throws IOException { + if (ctx == null || path == null) + return null; + InputStream is = null; + String ASSET_BASE = "file:///android_asset/"; + path = path.substring(ASSET_BASE.length()); + AssetManager assets = ctx.getAssets(); + is = assets.open(path); + return is; + } + + public static void copy(InputStream is, OutputStream os) throws IOException { + final byte[] bytes = new byte[4096]; + int len; + while ((len = is.read(bytes)) > 0) { + os.write(bytes, 0, len); + } + } + + public static void closeQuiet(InputStream stream) { + if (stream == null) + return; + try { + stream.close(); + } catch (IOException e) { + } + } + + public static void closeQuiet(OutputStream stream) { + if (stream == null) + return; + try { + stream.close(); + } catch (IOException e) { + } + } + + //Note: will not delete populated subdirs + public static void deleteFilesInDir(String dirPath) { + File fontDir = new File(dirPath); + File[] files = fontDir.listFiles(); + if (files != null) { + for(File file : fontDir.listFiles()) { + file.delete(); + } + } + } + + public static boolean hasNavigationBar(Context context) { + boolean needsNavigationBar = false; + try { + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + needsNavigationBar = wm.needsNavigationBar(); + } catch (RemoteException e) { + } + // Need to also check for devices with hardware keys where the user has chosen to use + // the on screen navigation bar + needsNavigationBar = needsNavigationBar || + CMSettings.Secure.getInt(context.getContentResolver(), + CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR, 0) == 1; + return needsNavigationBar; + } + + public static Bitmap loadBitmapBlob(Cursor cursor, int columnIdx) { + if (columnIdx < 0) { + Log.w(TAG, "loadBitmapBlob(): Invalid index provided, returning null"); + return null; + } + + if (cursor.getType(columnIdx) == Cursor.FIELD_TYPE_STRING) { + return loadBitmapFile(cursor, columnIdx); + } + + byte[] blob = cursor.getBlob(columnIdx); + if (blob == null) return null; + return BitmapFactory.decodeByteArray(blob, 0, blob.length); + } + + public static Bitmap loadBitmapFile(Cursor cursor, int columnIdx) { + if (columnIdx < 0) { + Log.w(TAG, "loadBitmapFile(): Invalid index provided, returning null"); + return null; + } + String path = cursor.getString(columnIdx); + if (TextUtils.isEmpty(path)) { + return null; + } + + Bitmap image = null; + FileInputStream inputStream; + try { + inputStream = new FileInputStream(path); + image = BitmapFactory.decodeStream(inputStream); + inputStream.close(); + } catch (Exception e) { + Log.w(TAG, "Unable to open preview " + path, e); + } + + return image; + } + + public static String getBatteryIndex(int type) { + switch(type) { + case 2: + return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_CIRCLE; + case 5: + return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE; + default: + return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_PORTRAIT; + } + } + + public static Bitmap getRegularWallpaperBitmap(Context context) { + WallpaperManager wm = WallpaperManager.getInstance(context); + + Bitmap bitmap = null; + // desktop wallpaper here + Bitmap wallpaper = wm.getBitmap(); + if (wallpaper == null) { + return null; + } + + Point size = new Point(); + WindowManager windowManager = + (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getRealSize(size); + + final int dw = size.x; + final int dh = size.y; + + // Center the scaled image + float scale = Math.max(1f, Math.max(dw / (float) wallpaper.getWidth(), + dh / (float) wallpaper.getHeight())); + + final int scaledWidth = Math.round((wallpaper.getWidth() * scale)); + final int scaledHeight = Math.round((wallpaper.getHeight() * scale)); + + // TODO: set xOffset to wm.getLastWallpaperX() once available + int xOffset = wm.getLastWallpaperX(); + // x offset + if (xOffset == -1) { + xOffset = 0; + } else { + xOffset *= -1; + } + + // y offsets + // TODO: set yOffset to wm.getLastWallpaperY() once available + int yOffset = wm.getLastWallpaperY(); + if (yOffset == -1) { + yOffset = 0; + } else { + yOffset *= -1; + } + + if (DEBUG) { + Log.d(TAG, "scale: " + scale); + Log.d(TAG, "scaledWidth: " + scaledWidth); + Log.d(TAG, "scaledHeight: " + scaledHeight); + Log.d(TAG, "wallpaper size: width: " + wallpaper.getWidth() + + ", height: " + wallpaper.getHeight()); + Log.d(TAG, "xOffset: " + xOffset); + Log.d(TAG, "yOffset: " + yOffset); + } + + try { + if (wallpaper.getHeight() < dh) { + // need to scale it up vertically + + if (wallpaper.getHeight() > wallpaper.getWidth()) { + // handle portrait wallpaper + float diff = scaledWidth - dw; + int diffhalf = Math.round(diff / 2); + + bitmap = Bitmap.createScaledBitmap(wallpaper, scaledWidth, scaledHeight, true); + bitmap = Bitmap.createBitmap(bitmap, diffhalf, 0, dw, dh); + bitmap = Bitmap.createBitmap(bitmap, xOffset, 0, dw, dh); + } else { + int goldenWidth = Math.round(wallpaper.getHeight() * 1.125f); + int spaceA = (wallpaper.getWidth() - goldenWidth) / 2; + int spaceB = (goldenWidth - Math.round(dh / scale)) / 2; + + bitmap = Bitmap.createBitmap(wallpaper, spaceA, 0, goldenWidth, + wallpaper.getHeight()); + int left = spaceB + Math.round(xOffset / scale); + bitmap = Bitmap.createBitmap(bitmap, left, 0, Math.round(dw / scale), + Math.round(dh / scale)); + } + + } else if (wallpaper.getWidth() < dw) { + // need to scale it up horizontally + + if (wallpaper.getHeight() > wallpaper.getWidth()) { + // handle portrait wallpaper + return wallpaper; + + } else { + // handle landscape wallpaper + float diff = wallpaper.getHeight() - wallpaper.getWidth(); + int diffhalf = Math.round(diff / 2); + + if (diffhalf < 0) { + return wallpaper; + } + + bitmap = Bitmap.createBitmap( + wallpaper, diffhalf, 0, + wallpaper.getWidth(), wallpaper.getWidth()); + + // blow it up + bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledWidth, true); + + bitmap = Bitmap.createBitmap(bitmap, 0, 0, dw, dh); + } + + } else { + // sometimes the wallpaper manager gives incorrect offsets, + // and adds like 200 pixels randomly. If it's bigger than we can handle, calculate + // our own :) + if (yOffset + dh > wallpaper.getHeight()) { + yOffset = (wallpaper.getHeight() - dh) / 2; + } + if (xOffset + dw > wallpaper.getWidth()) { + yOffset = (wallpaper.getWidth() - dw) / 2; + } + bitmap = Bitmap.createBitmap(wallpaper, xOffset, yOffset, dw, dh); + } + } catch (IllegalArgumentException e) { + // Cropping/resizing failed so return the original + bitmap = wallpaper; + } + return bitmap; + } + + public static boolean isRecentTaskHome(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + final List recentTasks = am.getRecentTasks( + 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); + if (recentTasks.size() > 1) { + ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(1); + + Intent intent = new Intent(recentInfo.baseIntent); + if (recentInfo.origActivity != null) { + intent.setComponent(recentInfo.origActivity); + } + + // Now check if this recent task is a launcher + if (isCurrentHomeActivity(context, intent.getComponent())) { + return true; + } + } + return false; + } + + public static boolean isRecentTaskThemeStore(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + final List recentTasks = am.getRecentTasks( + 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); + if (recentTasks.size() > 0) { + ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); + + Intent intent = new Intent(recentInfo.baseIntent); + if (recentInfo.origActivity != null) { + intent.setComponent(recentInfo.origActivity); + } + + if (intent.getComponent() + .getPackageName().equals(ChooserActivity.THEME_STORE_PACKAGE)) { + return true; + } + } + return false; + } + + + public static String getTopTaskPackageName(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + final List recentTasks = am.getRecentTasks(1, 0); + if (recentTasks.size() > 0) { + ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); + if (recentInfo.origActivity != null) { + return recentInfo.origActivity.getPackageName(); + } + if (recentInfo.baseIntent != null) { + return recentInfo.baseIntent.getComponent().getPackageName(); + } + } + return null; + } + + public static boolean hasPerAppThemesApplied(Context context) { + final Configuration config = context.getResources().getConfiguration(); + final ThemeConfig themeConfig = config != null ? config.themeConfig : null; + if (themeConfig != null) { + Map themes = themeConfig.getAppThemes(); + for (String appPkgName : themes.keySet()) { + if (ThemeUtils.isPerAppThemeComponent(appPkgName)) { + return true; + } + } + } + return false; + } + + /** + * Method to identify if a theme explicitly overlays a particular app. Explicit is defined + * as having files in overlays/appPkgName/ + * @param context + * @param appPkgNane + * @param themePkgName + * @return + */ + public static boolean themeHasOverlayForApp(Context context, String appPkgNane, + String themePkgName) { + boolean hasExplicitOverlay = false; + if (ThemeConfig.SYSTEM_DEFAULT.equals(themePkgName)) { + hasExplicitOverlay = true; + } else { + try { + Context themeContext = context.createPackageContext(themePkgName, 0); + if (themeContext != null) { + AssetManager assets = themeContext.getAssets(); + String[] files = assets.list(OVERLAY_BASE_PATH + appPkgNane); + if (files != null && files.length > 0) hasExplicitOverlay = true; + } + } catch (Exception e) { + // don't care, we'll return false and let the caller handle things + } + } + return hasExplicitOverlay; + } + + private static boolean isCurrentHomeActivity(Context context, + ComponentName component) { + final PackageManager pm = context.getPackageManager(); + ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + .resolveActivityInfo(pm, 0); + + return homeInfo != null + && homeInfo.packageName.equals(component.getPackageName()) + && homeInfo.name.equals(component.getClassName()); + } + + /** + * Returns the resource-IDs for all attributes specified in the given + * -resource tag as an int array. + * stackoverflow.com/questions/13816596/accessing-declare-styleable-resources-programatically + * + * @param name + * @return + */ + public static final int[] getResourceDeclareStyleableIntArray(String pkgName, String name) { + try { + //use reflection to access the resource class + Field[] fields2 = + Class.forName(pkgName + ".R$styleable").getFields(); + + //browse all fields + for (Field f : fields2) { + //pick matching field + if (f.getName().equals(name)) { + //return as int array + int[] ret = (int[])f.get(null); + return ret; + } + } + } + catch (Throwable t) { + } + + return null; + } + + /** + * Retrieves the list of dangerous permissions not granted to the supplied package. This method + * is not capable of identifying if a given permission was previously revoked by the user or + * if the user decided not to be asked again. + * + * @param context + * @param pkgName + * @return Returns an array of Strings with the name of the permissions. An empty array will + * be returned if all dangerous permissions have been already granted. + */ + public static String[] getDangerousPermissionsNotGranted(Context context, String pkgName) + throws InvalidParameterException { + LinkedList permissionsNotGranted = new LinkedList(); + PackageInfo pkgInfo = null; + PackageManager pm = context.getPackageManager(); + try { + pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + throw new InvalidParameterException("Package " + pkgName + " not found"); + } + + String[] requestedPermissions = pkgInfo.requestedPermissions; + int[] requestedPermissionsFlags = pkgInfo.requestedPermissionsFlags; + + for (int indx = 0; indx < requestedPermissions.length; indx++) { + if ((requestedPermissionsFlags[indx] & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { + try { + PermissionInfo pi = pm.getPermissionInfo(requestedPermissions[indx],0); + + if (pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + permissionsNotGranted.add(requestedPermissions[indx]); + if (DEBUG) { + Log.d(TAG, "Permission " + requestedPermissions[indx] + "not granted"); + } + } + } catch(PackageManager.NameNotFoundException e) { + //If package manager doesn't know of the permission we just continue since + //this permission won't end up in the list + } + } + } + return permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]); + } + + /** + * Builds a ComponentName to identify the activity associated with the + * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category in the given package + * @param context + * @param packageName + * @return Returns the ComponentName or null if no activity was found. + */ + private static ComponentName getPermissionGranterComponentName(Context context, + String packageName) { + Intent rIntent = new Intent() + .setPackage(packageName) + .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION); + + List resolveInfo = context.getPackageManager(). + queryIntentActivities(rIntent, PackageManager.GET_RESOLVED_FILTER); + + if (resolveInfo.size() >= 1) { + if (DEBUG) { + if (resolveInfo.size() >= 2) { + Log.w(TAG, "Got " + resolveInfo.size() + " resolvers! Defaulting to " + + resolveInfo.get(0).activityInfo.name); + } + } + } + return (resolveInfo.size() >=1 ) ? + new ComponentName(packageName, resolveInfo.get(0).activityInfo.name) : + null; + } + + /** + * Builds an intent used to request the user to grant or revoke the supplied permissions. + * The intent will set the KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION + * category and an extra containing the list of permissions identified by + * KeyguardExternalView.EXTRA_PERMISSION_LIST + * @param context + * @param packageName + * @param permissionList + * @return Returns the intent if an activity associated with + * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category was found. Otherwise, it + * returns null + */ + public static Intent buildPermissionGrantRequestIntent(Context context, String packageName, + String[] permissionList) { + ComponentName componentName = getPermissionGranterComponentName(context, packageName); + if (componentName == null) return null; + + Intent permissionIntent = new Intent() + .setComponent(componentName) + .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION) + .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(KeyguardExternalView.EXTRA_PERMISSION_LIST, permissionList); + return permissionIntent; + } + + public static String getDefaultThemePackageName(Context context) { + final String defaultThemePkg = CMSettings.Secure.getString(context.getContentResolver(), + CMSettings.Secure.DEFAULT_THEME_PACKAGE); + if (!TextUtils.isEmpty(defaultThemePkg)) { + PackageManager pm = context.getPackageManager(); + try { + if (pm.getPackageInfo(defaultThemePkg, 0) != null) { + return defaultThemePkg; + } + } catch (PackageManager.NameNotFoundException e) { + // doesn't exist so system will be default + Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e); + } + } + + return SYSTEM_DEFAULT; + } +} diff --git a/src/org/cyanogenmod/theme/util/WallpaperUtils.java b/src/org/cyanogenmod/theme/util/WallpaperUtils.java new file mode 100644 index 0000000..c07b99b --- /dev/null +++ b/src/org/cyanogenmod/theme/util/WallpaperUtils.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.util; + +import android.app.WallpaperManager; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +public class WallpaperUtils { + private static final String TAG = WallpaperUtils.class.getSimpleName(); + private static final int DEFAULT_COMPRESS_QUALITY = 90; + + /** + * createThumbnail from WallpaperCropActivity in f/b/packages/WallpaperCropper, renamed + * tp createPreview. + */ + public static Bitmap createPreview(Point size, Context context, Uri uri, byte[] imageBytes, + Resources res, int resId, int rotation, boolean leftAligned) { + int width = size.x; + int height = size.y; + + BitmapCropTask cropTask; + if (uri != null) { + cropTask = new BitmapCropTask( + context, uri, null, rotation, width, height, false, true, null); + } else if (imageBytes != null) { + cropTask = new BitmapCropTask( + imageBytes, null, rotation, width, height, false, true, null); + } else { + cropTask = new BitmapCropTask( + context, res, resId, null, rotation, width, height, false, true, null); + } + Point bounds = cropTask.getImageBounds(); + if (bounds == null || bounds.x == 0 || bounds.y == 0) { + return null; + } + + Matrix rotateMatrix = new Matrix(); + rotateMatrix.setRotate(rotation); + float[] rotatedBounds = new float[] { bounds.x, bounds.y }; + rotateMatrix.mapPoints(rotatedBounds); + rotatedBounds[0] = Math.abs(rotatedBounds[0]); + rotatedBounds[1] = Math.abs(rotatedBounds[1]); + + RectF cropRect = getMaxCropRect( + (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); + cropTask.setCropBounds(cropRect); + + if (cropTask.cropBitmap()) { + return cropTask.getCroppedBitmap(); + } else { + return null; + } + } + + /** + * getMaxCropRect from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static RectF getMaxCropRect( + int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { + RectF cropRect = new RectF(); + // Get a crop rect that will fit this + if (inWidth / (float) inHeight > outWidth / (float) outHeight) { + cropRect.top = 0; + cropRect.bottom = inHeight; + cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; + cropRect.right = inWidth - cropRect.left; + if (leftAligned) { + cropRect.right -= cropRect.left; + cropRect.left = 0; + } + } else { + cropRect.left = 0; + cropRect.right = inWidth; + cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; + cropRect.bottom = inHeight - cropRect.top; + } + return cropRect; + } + + /** + * convertExtensionToCompressFormat from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static Bitmap.CompressFormat convertExtensionToCompressFormat(String extension) { + return extension.equals("png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; + } + + /** + * getFileExtension from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static String getFileExtension(String requestFormat) { + String outputFormat = (requestFormat == null) + ? "jpg" + : requestFormat; + outputFormat = outputFormat.toLowerCase(); + return (outputFormat.equals("png") || outputFormat.equals("gif")) + ? "png" // We don't support gif compression. + : "jpg"; + } + + /** + * BitmapCropTask from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static class BitmapCropTask extends AsyncTask { + Uri mInUri = null; + Context mContext; + String mInFilePath; + byte[] mInImageBytes; + int mInResId = 0; + InputStream mInStream; + RectF mCropBounds = null; + int mOutWidth, mOutHeight; + int mRotation; + String mOutputFormat = "jpg"; // for now + boolean mSetWallpaper; + boolean mSaveCroppedBitmap; + Bitmap mCroppedBitmap; + Runnable mOnEndRunnable; + Resources mResources; + OnBitmapCroppedHandler mOnBitmapCroppedHandler; + boolean mNoCrop; + boolean mImageFromAsset; + + public BitmapCropTask(byte[] imageBytes, + RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mInImageBytes = imageBytes; + init(cropBounds, rotation, + outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); + } + + public BitmapCropTask(Context c, Uri inUri, + RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mContext = c; + mInUri = inUri; + init(cropBounds, rotation, + outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); + } + + public BitmapCropTask(Context c, Resources res, int inResId, + RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mContext = c; + mInResId = inResId; + mResources = res; + init(cropBounds, rotation, + outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); + } + + private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mCropBounds = cropBounds; + mRotation = rotation; + mOutWidth = outWidth; + mOutHeight = outHeight; + mSetWallpaper = setWallpaper; + mSaveCroppedBitmap = saveCroppedBitmap; + mOnEndRunnable = onEndRunnable; + } + + // Helper to setup input stream + private void regenerateInputStream() { + if (mInUri == null && mInResId == 0 && mInFilePath == null && + mInImageBytes == null && !mImageFromAsset) { + Log.w(TAG, "cannot read original file, no input URI, resource ID, or " + + "image byte array given"); + } else { + Utils.closeQuiet(mInStream); + try { + if (mInUri != null) { + mInStream = new BufferedInputStream( + mContext.getContentResolver().openInputStream(mInUri)); + } else if (mInFilePath != null) { + mInStream = mContext.openFileInput(mInFilePath); + } else if (mInImageBytes != null) { + mInStream = new BufferedInputStream( + new ByteArrayInputStream(mInImageBytes)); + } else { + mInStream = new BufferedInputStream( + mResources.openRawResource(mInResId)); + } + } catch (FileNotFoundException e) { + Log.w(TAG, "cannot read file: " + mInUri.toString(), e); + } + } + } + + public Point getImageBounds() { + regenerateInputStream(); + if (mInStream != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(mInStream, null, options); + if (options.outWidth != 0 && options.outHeight != 0) { + return new Point(options.outWidth, options.outHeight); + } + } + return null; + } + + public void setCropBounds(RectF cropBounds) { + mCropBounds = cropBounds; + } + + public Bitmap getCroppedBitmap() { + return mCroppedBitmap; + } + public boolean cropBitmap() { + boolean failure = false; + + regenerateInputStream(); + + WallpaperManager wallpaperManager = null; + if (mSetWallpaper) { + wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); + } + if (mSetWallpaper && mNoCrop && mInStream != null) { + try { + wallpaperManager.setStream(mInStream); + } catch (IOException e) { + Log.w(TAG, "cannot write stream to wallpaper", e); + failure = true; + } + return !failure; + } + if (mInStream != null) { + // Find crop bounds (scaled to original image size) + Rect roundedTrueCrop = new Rect(); + Matrix rotateMatrix = new Matrix(); + Matrix inverseRotateMatrix = new Matrix(); + if (mRotation > 0) { + rotateMatrix.setRotate(mRotation); + inverseRotateMatrix.setRotate(-mRotation); + + mCropBounds.roundOut(roundedTrueCrop); + mCropBounds = new RectF(roundedTrueCrop); + + Point bounds = getImageBounds(); + + float[] rotatedBounds = new float[] { bounds.x, bounds.y }; + rotateMatrix.mapPoints(rotatedBounds); + rotatedBounds[0] = Math.abs(rotatedBounds[0]); + rotatedBounds[1] = Math.abs(rotatedBounds[1]); + + mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); + inverseRotateMatrix.mapRect(mCropBounds); + mCropBounds.offset(bounds.x/2, bounds.y/2); + + regenerateInputStream(); + } + + mCropBounds.roundOut(roundedTrueCrop); + + if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { + Log.w(TAG, "crop has bad values for full size image"); + failure = true; + return false; + } + + // See how much we're reducing the size of the image + int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, + roundedTrueCrop.height() / mOutHeight); + + // Attempt to open a region decoder + BitmapRegionDecoder decoder = null; + try { + decoder = BitmapRegionDecoder.newInstance(mInStream, true); + } catch (IOException e) { + Log.w(TAG, "cannot open region decoder for file: " + mInUri.toString(), e); + } + + Bitmap crop = null; + if (decoder != null) { + // Do region decoding to get crop bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + if (scaleDownSampleSize > 1) { + options.inSampleSize = scaleDownSampleSize; + } + crop = decoder.decodeRegion(roundedTrueCrop, options); + decoder.recycle(); + } + + if (crop == null) { + // BitmapRegionDecoder has failed, try to crop in-memory + regenerateInputStream(); + Bitmap fullSize = null; + if (mInStream != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + if (scaleDownSampleSize > 1) { + options.inSampleSize = scaleDownSampleSize; + } + fullSize = BitmapFactory.decodeStream(mInStream, null, options); + } + if (fullSize != null) { + mCropBounds.left /= scaleDownSampleSize; + mCropBounds.top /= scaleDownSampleSize; + mCropBounds.bottom /= scaleDownSampleSize; + mCropBounds.right /= scaleDownSampleSize; + mCropBounds.roundOut(roundedTrueCrop); + + crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, + roundedTrueCrop.top, roundedTrueCrop.width(), + roundedTrueCrop.height()); + } + } + + if (crop == null) { + Log.w(TAG, "cannot decode file: " + mInUri.toString()); + failure = true; + return false; + } + if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { + float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; + rotateMatrix.mapPoints(dimsAfter); + dimsAfter[0] = Math.abs(dimsAfter[0]); + dimsAfter[1] = Math.abs(dimsAfter[1]); + + if (!(mOutWidth > 0 && mOutHeight > 0)) { + mOutWidth = Math.round(dimsAfter[0]); + mOutHeight = Math.round(dimsAfter[1]); + } + + RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); + RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); + + Matrix m = new Matrix(); + if (mRotation == 0) { + m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); + } else { + Matrix m1 = new Matrix(); + m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); + Matrix m2 = new Matrix(); + m2.setRotate(mRotation); + Matrix m3 = new Matrix(); + m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); + Matrix m4 = new Matrix(); + m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); + + Matrix c1 = new Matrix(); + c1.setConcat(m2, m1); + Matrix c2 = new Matrix(); + c2.setConcat(m4, m3); + m.setConcat(c2, c1); + } + + Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), + (int) returnRect.height(), Bitmap.Config.ARGB_8888); + if (tmp != null) { + Canvas c = new Canvas(tmp); + Paint p = new Paint(); + p.setFilterBitmap(true); + c.drawBitmap(crop, m, p); + crop = tmp; + } + } + + if (mSaveCroppedBitmap) { + mCroppedBitmap = crop; + } + + // Get output compression format + Bitmap.CompressFormat cf = + convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); + + // Compress to byte array + ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); + if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { + // If we need to set to the wallpaper, set it + if (mSetWallpaper && wallpaperManager != null) { + try { + byte[] outByteArray = tmpOut.toByteArray(); + wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); + if (mOnBitmapCroppedHandler != null) { + mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); + } + } catch (IOException e) { + Log.w(TAG, "cannot write stream to wallpaper", e); + failure = true; + } + } + } else { + Log.w(TAG, "cannot compress bitmap"); + failure = true; + } + } + return !failure; // True if any of the operations failed + } + + @Override + protected Boolean doInBackground(Void... params) { + return cropBitmap(); + } + + @Override + protected void onPostExecute(Boolean result) { + if (mOnEndRunnable != null) { + mOnEndRunnable.run(); + } + } + + public interface OnBitmapCroppedHandler { + public void onBitmapCropped(byte[] imageBytes); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java b/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java new file mode 100644 index 0000000..29534f7 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; + +public class AutoSnapHorizontalScrollView extends HorizontalScrollView { + private static final int SNAP_ON_UP_DELAY = 250; + + private int mScrollPositionOnUp; + + enum EventStates { + SCROLLING, + FLING + } + + private EventStates mSystemState = EventStates.SCROLLING; + + public AutoSnapHorizontalScrollView(Context context) { + super(context); + } + + public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + Runnable mSnapRunnable = new Runnable(){ + @Override + public void run() { + snapItems(); + mSystemState = EventStates.SCROLLING; + } + }; + + /** + * Added runnable for snapping on an item when the user lifts up their finger and + * there is no scrolling taking place (i.e. no flinging) + */ + Runnable mSnapOnUpRunnable = new Runnable(){ + @Override + public void run() { + int scrollX = getScrollX(); + if (scrollX != mScrollPositionOnUp) { + mScrollPositionOnUp = scrollX; + postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); + } else { + snapItems(); + mSystemState = EventStates.SCROLLING; + } + } + }; + + @Override + public boolean onTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mSystemState = EventStates.FLING; + removeCallbacks(mSnapRunnable); + mScrollPositionOnUp = getScrollX(); + postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); + } else if (action == MotionEvent.ACTION_DOWN) { + mSystemState = EventStates.SCROLLING; + removeCallbacks(mSnapRunnable); + removeCallbacks(mSnapOnUpRunnable); + } + return super.onTouchEvent(ev); + } + + private void snapItems() { + Rect parentBounds = new Rect(); + getDrawingRect(parentBounds); + Rect childBounds = new Rect(); + ViewGroup parent = (ViewGroup) getChildAt(0); + for (int i = 0; i < parent.getChildCount(); i++) { + View view = parent.getChildAt(i); + view.getHitRect(childBounds); + if (childBounds.right >= parentBounds.left && childBounds.left <= parentBounds.left) { + // First partially visible child + if ((childBounds.right - parentBounds.left) >= + (parentBounds.left - childBounds.left)) { + smoothScrollTo(Math.abs(childBounds.left), 0); + } else { + /** + * Added code to take into account dividers so that we do not see + * one on the edge of the screen when items snap in place. + */ + int dividerWidth = 0; + if (parent instanceof LinearLayout) { + dividerWidth = ((LinearLayout) parent).getDividerWidth(); + } + smoothScrollTo(Math.abs(childBounds.right) + dividerWidth, 0); + } + break; + } + } + } + + // Overwrite measureChildX as we want our child to be able to tell the + // parents width but do not impose any limits (AT_MOST; AT_MAX) + @Override + protected void measureChild(View child, int parentWidthMeasureSpec, + int parentHeightMeasureSpec) { + ViewGroup.LayoutParams lp = child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + + mPaddingBottom, lp.height); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) - (mPaddingLeft + mPaddingRight), + MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) - + (mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed), MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + if (mSystemState == EventStates.SCROLLING) { + return; + } + if (Math.abs(l - oldl) <= 1 && mSystemState == EventStates.FLING) { + removeCallbacks(mSnapRunnable); + postDelayed(mSnapRunnable, 100); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/BootAniImageView.java b/src/org/cyanogenmod/theme/widget/BootAniImageView.java new file mode 100644 index 0000000..b3f58a6 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/BootAniImageView.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.ImageView; + +import java.io.IOException; +import java.util.List; +import java.util.zip.ZipFile; + +import libcore.io.IoUtils; + +import org.cyanogenmod.theme.util.BootAnimationHelper; + +public class BootAniImageView extends ImageView { + private static final String TAG = BootAniImageView.class.getName(); + + private static final boolean DEBUG = false; + + private static final int MAX_BUFFERS = 2; + + private Bitmap[] mBuffers = new Bitmap[MAX_BUFFERS]; + private int mReadBufferIndex = 0; + private int mWriteBufferIndex = 0; + private ZipFile mBootAniZip; + + private List mAnimationParts; + private int mCurrentPart; + private int mCurrentFrame; + private int mCurrentPartPlayCount; + private int mFrameDuration; + + private boolean mActive = false; + + public BootAniImageView(Context context) { + this(context, null); + } + + public BootAniImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BootAniImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility == VISIBLE) { + if (mBootAniZip != null) start(); + } else { + stop(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + // In case we end up in the mid dle of onDraw while the buffers are being recycled + // we catch the exception and just let frame not be rendered. + try { + super.onDraw(canvas); + } catch (RuntimeException e) { + Log.e(TAG, "Unable to draw boot animation frame."); + } + } + + public synchronized boolean setBootAnimation(ZipFile bootAni) { + if (bootAni == null) { + Log.w(TAG, "Boot animation ZipFile is null."); + return false; + } + // make sure we are stopped first + stop(); + + if (mBootAniZip != null) { + IoUtils.closeQuietly(mBootAniZip); + // This boot animation may be a different size than the previous + // one so clear out the buffers so they can be recreated with + // the correct size. + for (int i = 0; i < MAX_BUFFERS; i++) { + if (mBuffers[i] != null) { + mBuffers[i].recycle(); + mBuffers[i] = null; + } + } + } + mBootAniZip = bootAni; + + try { + mAnimationParts = BootAnimationHelper.parseAnimation(mBootAniZip); + } catch (Exception e) { + // "Gotta catch 'em all" + Log.e(TAG, "Unable to set boot animation", e); + mAnimationParts = null; + mBootAniZip = null; + return false; + } + + if (mAnimationParts == null || mAnimationParts.size() == 0) { + return false; + } + + final BootAnimationHelper.AnimationPart part = mAnimationParts.get(0); + mCurrentPart = 0; + mCurrentPartPlayCount = part.playCount; + mFrameDuration = part.frameRateMillis; + mWriteBufferIndex = mReadBufferIndex = 0; + mCurrentFrame = 0; + + getNextFrame(); + + return true; + } + + private void getNextFrame() { + if (mAnimationParts == null || mAnimationParts.size() == 0) return; + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inBitmap = mBuffers[mWriteBufferIndex]; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + opts.inMutable = true; + final BootAnimationHelper.AnimationPart part = mAnimationParts.get(mCurrentPart); + try { + mBuffers[mWriteBufferIndex] = + BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( + part.frames.get(mCurrentFrame++))), null, opts); + } catch (IllegalArgumentException iae) { + // In case we're here because the bitmap could not be re-used, try creating a new one + opts.inBitmap = null; + try { + if (DEBUG) { + Log.d(TAG, "Trying to load frame without reusing existing bitmap", iae); + } + if (mBuffers[mWriteBufferIndex] != null) { + // clean up our old bitmap + mBuffers[mWriteBufferIndex].recycle(); + mBuffers[mWriteBufferIndex] = null; + } + mBuffers[mWriteBufferIndex] = + BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( + part.frames.get(mCurrentFrame++))), null, opts); + } catch (Exception e) { + // Still failling? Let's log it and carry on. + Log.w(TAG, "Unable to get next frame", e); + } + } catch (IOException e) { + Log.w(TAG, "Unable to get next frame", e); + } + mWriteBufferIndex = (mWriteBufferIndex + 1) % MAX_BUFFERS; + if (mCurrentFrame >= part.frames.size()) { + if (mCurrentPartPlayCount > 0) { + if (--mCurrentPartPlayCount == 0) { + mCurrentPart++; + if (mCurrentPart >= mAnimationParts.size()) mCurrentPart = 0; + mCurrentFrame = 0; + mCurrentPartPlayCount = mAnimationParts.get(mCurrentPart).playCount; + } else { + mCurrentFrame = 0; + } + } else { + mCurrentFrame = 0; + } + } + } + + public void start() { + if (mAnimationParts == null) return; + + mActive = true; + post(mUpdateAnimationRunnable); + } + + public void stop() { + mActive = false; + removeCallbacks(mUpdateImageRunnable); + removeCallbacks(mUpdateAnimationRunnable); + } + + private Runnable mUpdateAnimationRunnable = new Runnable() { + @Override + public void run() { + if (!mActive) return; + BootAniImageView.this.postDelayed(mUpdateAnimationRunnable, mFrameDuration); + if (!isOffScreen()) { + BootAniImageView.this.post(mUpdateImageRunnable); + mReadBufferIndex = (mReadBufferIndex + 1) % MAX_BUFFERS; + getNextFrame(); + } + } + }; + + private Runnable mUpdateImageRunnable = new Runnable() { + @Override + public void run() { + setImageBitmap(mBuffers[mReadBufferIndex]); + } + }; + + private boolean isOffScreen() { + int[] pos = new int[2]; + getLocationOnScreen(pos); + return pos[1] >= mContext.getResources().getDisplayMetrics().heightPixels; + } +} diff --git a/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java b/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java new file mode 100644 index 0000000..e00e67a --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.cyanogenmod.theme.chooser.R; + +public class ConfirmCancelOverlay extends FrameLayout { + + private View mAcceptButton; + private View mCancelButton; + private TextView mTitle; + + private OnOverlayDismissedListener mListener; + + public ConfirmCancelOverlay(Context context) { + this(context, null); + } + + public ConfirmCancelOverlay(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ConfirmCancelOverlay(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mAcceptButton = findViewById(R.id.accept); + mCancelButton = findViewById(R.id.cancel); + mTitle = (TextView) findViewById(R.id.overlay_title); + + mAcceptButton.setOnClickListener(mClickListener); + mCancelButton.setOnClickListener(mClickListener); + } + + public void setTitle(CharSequence title) { + mTitle.setText(title); + } + + public void setTitle(int resId) { + mTitle.setText(resId); + } + + public void setOnOverlayDismissedListener(OnOverlayDismissedListener listener) { + mListener = listener; + } + + public void dismiss() { + if (mListener != null) { + mListener.onDismissed(false); + } + } + + private OnClickListener mClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onDismissed(v == mAcceptButton); + } + } + }; + + public interface OnOverlayDismissedListener { + public void onDismissed(boolean accepted); + } +} diff --git a/src/org/cyanogenmod/theme/widget/FittedTextView.java b/src/org/cyanogenmod/theme/widget/FittedTextView.java new file mode 100644 index 0000000..2451d25 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/FittedTextView.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Paint; +import android.text.method.TransformationMethod; +import android.text.method.AllCapsTransformationMethod; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * Change the font size to match the measured + * textview size by width + * + */ +public class FittedTextView extends TextView { + private Paint mPaint; + //If set to true, the text will be resized to fit the view. + private boolean mAutoFitText = true; + //Used to instruct whether the text should be expanded to fill out the view, even if the text + //fits without being resized + private boolean mAutoExpand = true; + + public FittedTextView(Context context) { + this(context, null); + } + + public FittedTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FittedTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mPaint = new Paint(); + } + + protected void setAutoFitText(boolean autoFit) { + mAutoFitText = autoFit; + } + + protected boolean getAutoFitText() { + return mAutoFitText; + } + + protected void setAutoExpand(boolean autoExpand) { + mAutoExpand = autoExpand; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (!mAutoFitText) return; + + final float THRESHOLD = 0.5f; + final float TARGET_WIDTH = getMeasuredWidth(); + String text = getText().toString(); + TransformationMethod tm = getTransformationMethod(); + if (tm != null && tm instanceof AllCapsTransformationMethod) { + text = getText().toString().toUpperCase(); + } + mPaint.set(getPaint()); + + if (mPaint.measureText(text) <= TARGET_WIDTH && !mAutoExpand) return; + + float max = 200; + float min = 2; + while(max > min) { + float size = (max+min) / 2; + mPaint.setTextSize(size); + float measuredWidth = mPaint.measureText(text); + if (Math.abs(TARGET_WIDTH - measuredWidth) <= THRESHOLD) { + break; + } else if (measuredWidth > TARGET_WIDTH) { + max = size-1; + } else { + min = size+1; + } + } + this.setTextSize(TypedValue.COMPLEX_UNIT_PX, min-1); + } +} diff --git a/src/org/cyanogenmod/theme/widget/LatoTextView.java b/src/org/cyanogenmod/theme/widget/LatoTextView.java new file mode 100644 index 0000000..0cb02ae --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/LatoTextView.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.util.AttributeSet; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +import java.io.File; + +/** + * A custom TextView that always uses the Lato font + */ +public class LatoTextView extends FittedTextView { + private static final int NUM_TYPEFACE_PER_FAMILY = 4; + + private static final String FONT_ASSSET_DIR = "fonts"; + // Regular fonts + private static final String LATO_REGULAR_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Regular.ttf"; + private static final String LATO_REGULAR_BOLD_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegBold.ttf"; + private static final String LATO_REGULAR_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegItalic.otf"; + private static final String LATO_REGULAR_BOLD_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegBoldItalic.ttf"; + // Condensed fonts + private static final String LATO_CONDENSED_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Cond.ttf"; + private static final String LATO_CONDENSED_BOLD_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondBold.ttf"; + private static final String LATO_CONDENSED_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondItalic.ttf"; + private static final String LATO_CONDENSED_BOLD_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondBoldItalic.ttf"; + // Light fonts + private static final String LATO_LIGHT_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Light.ttf"; + private static final String LATO_LIGHT_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-LightItalic.ttf"; + + private static final String CONDENSED = "condensed"; + private static final String LIGHT = "light"; + + private static Typeface[] sLatoRegularTypeface; + private static Typeface[] sLatoCondensedTypeface; + private static Typeface[] sLatoLightTypeface; + private static final Object sLock = new Object(); + + // Retrieving these attributes is done via reflection so let's just do this once and share + // it amongst the other instances of LatoTextView + private static int[] sTextViewStyleAttributes; + + public LatoTextView(Context context) { + this(context, null); + } + + public LatoTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LatoTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + synchronized (sLock) { + AssetManager assets = context.getAssets(); + if (sLatoRegularTypeface == null) { + sLatoRegularTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoRegularTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_REGULAR_PATH); + sLatoRegularTypeface[Typeface.BOLD] = + Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_PATH); + sLatoRegularTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_REGULAR_ITALIC_PATH); + sLatoRegularTypeface[Typeface.BOLD_ITALIC] = + Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_ITALIC_PATH); + } + if (sLatoCondensedTypeface == null) { + sLatoCondensedTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoCondensedTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_CONDENSED_PATH); + sLatoCondensedTypeface[Typeface.BOLD] = + Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_PATH); + sLatoCondensedTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_CONDENSED_ITALIC_PATH); + sLatoCondensedTypeface[Typeface.BOLD_ITALIC] = + Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_ITALIC_PATH); + } + if (sLatoLightTypeface == null) { + sLatoLightTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoLightTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_LIGHT_PATH); + sLatoLightTypeface[Typeface.BOLD] = + sLatoRegularTypeface[Typeface.BOLD]; + sLatoLightTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_LIGHT_ITALIC_PATH); + sLatoLightTypeface[Typeface.BOLD_ITALIC] = + sLatoRegularTypeface[Typeface.BOLD_ITALIC]; + } + } + + final Resources.Theme theme = context.getTheme(); + if (sTextViewStyleAttributes == null) { + sTextViewStyleAttributes = + Utils.getResourceDeclareStyleableIntArray("com.android.internal", "TextView"); + } + + if (sTextViewStyleAttributes != null) { + TypedArray a = + theme.obtainStyledAttributes(attrs, sTextViewStyleAttributes, defStyle, 0); + String fontFamily = "sans-serif"; + int styleIndex = Typeface.NORMAL; + if (a != null) { + int n = a.getIndexCount(); + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + final Resources res = getResources(); + int attrFontFamily = + res.getIdentifier("TextView_fontFamily", "styleable", "android"); + int attrTextStyle = + res.getIdentifier("TextView_textStyle", "styleable", "android"); + if (attr == attrFontFamily) { + fontFamily = a.getString(attr); + } else if (attr == attrTextStyle) { + styleIndex = a.getInt(attr, styleIndex); + } + } + a.recycle(); + } + + setTypefaceFromAttrs(fontFamily, styleIndex); + setAutoExpand(false); + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, + R.styleable.FittedTextView, 0, 0); + try { + //Although we extend FittedTextView, we don't want all instances to auto fit the + //text, so we check if autoFitText has been set in the attributes. Default to false + boolean fit = styledAttrs.getBoolean(R.styleable.FittedTextView_autoFitText, false); + setAutoFitText(fit); + } finally { + styledAttrs.recycle(); + } + } + } + + private void setTypefaceFromAttrs(String familyName, int styleIndex) { + Typeface tf = null; + if (familyName != null) { + Typeface[] typefaces = sLatoRegularTypeface; + if (familyName.contains(CONDENSED)) { + typefaces = sLatoCondensedTypeface; + } else if (familyName.contains(LIGHT)) { + typefaces = sLatoLightTypeface; + } + tf = typefaces[styleIndex]; + if (tf != null) { + setTypeface(tf); + return; + } + } + setTypeface(tf, styleIndex); + } +} diff --git a/src/org/cyanogenmod/theme/widget/LockableScrollView.java b/src/org/cyanogenmod/theme/widget/LockableScrollView.java new file mode 100644 index 0000000..b4c9ffc --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/LockableScrollView.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ScrollView; + +public class LockableScrollView extends ScrollView { + private boolean mScrollingEnabled = true; + + public LockableScrollView(Context context) { + this(context, null); + } + + public LockableScrollView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LockableScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setScrollingEnabled(boolean enabled) { + mScrollingEnabled = enabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mScrollingEnabled && super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + return mScrollingEnabled && super.onTouchEvent(ev); + default: + return super.onTouchEvent(ev); + } + } + + @Override + public void setOverScrollMode(int mode) { + // Some themes can cause theme chooser to crash when creating the EdgeEffects for + // the scroll view. If an exception occurs we fallback to no overscroll + try { + super.setOverScrollMode(mode); + } catch (Exception e) { + super.setOverScrollMode(OVER_SCROLL_NEVER); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/NavBarSpace.java b/src/org/cyanogenmod/theme/widget/NavBarSpace.java new file mode 100644 index 0000000..720df29 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/NavBarSpace.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import org.cyanogenmod.theme.util.Utils; + +/** + * A simple view used to pad layouts so that content floats above the + * navigation bar. This is best used with transparent or translucent + * navigation bars where the content can go behind them. + */ +public class NavBarSpace extends View { + + public NavBarSpace(Context context) { + this(context, null); + } + + public NavBarSpace(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NavBarSpace(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (!Utils.hasNavigationBar(getContext())) { + this.setVisibility(View.GONE); + } else { + this.setVisibility(View.VISIBLE); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java b/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java new file mode 100644 index 0000000..18e3da2 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.cyanogenmod.theme.chooser.R; + +public class ThemeTagLayout extends LinearLayout { + private ImageView mAppliedTag; + private TextView mCustomizedTag; + private TextView mUpdatedTag; + private TextView mDefaultTag; + private TextView mLegacyTag; + + public ThemeTagLayout(Context context) { + this(context, null); + } + + public ThemeTagLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ThemeTagLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + LayoutInflater inflater = + (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mAppliedTag = (ImageView) inflater.inflate(R.layout.tag_applied, this, false); + mCustomizedTag = (TextView) inflater.inflate(R.layout.tag_customized, this, false); + mUpdatedTag = (TextView) inflater.inflate(R.layout.tag_updated, this, false); + mDefaultTag = (TextView) inflater.inflate(R.layout.tag_default, this, false); + mLegacyTag = (TextView) inflater.inflate(R.layout.tag_legacy, this, false); + } + + public void setAppliedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_applied) != null) return; + addView(mAppliedTag, 0); + } else { + if (findViewById(R.id.tag_applied) == null) return; + removeView(mAppliedTag); + } + } + + public void setCustomizedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_customized) != null) return; + final int childCount = getChildCount(); + if (childCount > 1) { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child != mAppliedTag) { + addView(mCustomizedTag, i); + break; + } + } + } else { + addView(mCustomizedTag); + } + } else { + if (findViewById(R.id.tag_customized) == null) return; + removeView(mCustomizedTag); + } + } + + public boolean isCustomizedTagEnabled() { + return findViewById(R.id.tag_customized) != null; + } + + public void setUpdatedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_updated) != null) return; + final int childCount = getChildCount(); + if (childCount > 2) { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child != mAppliedTag && child != mCustomizedTag) { + addView(mUpdatedTag, i); + break; + } + } + } else { + addView(mUpdatedTag); + } + } else { + if (findViewById(R.id.tag_updated) == null) return; + removeView(mUpdatedTag); + } + } + + public boolean isUpdatedTagEnabled() { + return findViewById(R.id.tag_updated) != null; + } + + public void setDefaultTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_default) != null) return; + addView(mDefaultTag); + } else { + if (findViewById(R.id.tag_default) == null) return; + removeView(mDefaultTag); + } + } + + public void setLegacyTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_legacy) != null) return; + addView(mLegacyTag); + } else { + if (findViewById(R.id.tag_legacy) == null) return; + removeView(mLegacyTag); + } + } +} -- cgit v1.1