diff --git a/res/anim/floating_action_button_elevation.xml b/res/anim/floating_action_button_elevation.xml new file mode 100644 index 00000000..8b33d89e --- /dev/null +++ b/res/anim/floating_action_button_elevation.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/res/drawable-hdpi/ic_grabber.png b/res/drawable-hdpi/ic_grabber.png new file mode 100644 index 00000000..ce5dab7b Binary files /dev/null and b/res/drawable-hdpi/ic_grabber.png differ diff --git a/res/drawable-mdpi/ic_grabber.png b/res/drawable-mdpi/ic_grabber.png new file mode 100644 index 00000000..d5f2c16c Binary files /dev/null and b/res/drawable-mdpi/ic_grabber.png differ diff --git a/res/drawable-xhdpi/ic_grabber.png b/res/drawable-xhdpi/ic_grabber.png new file mode 100644 index 00000000..418fcdc1 Binary files /dev/null and b/res/drawable-xhdpi/ic_grabber.png differ diff --git a/res/drawable-xxhdpi/ic_grabber.png b/res/drawable-xxhdpi/ic_grabber.png new file mode 100644 index 00000000..64f2f0c0 Binary files /dev/null and b/res/drawable-xxhdpi/ic_grabber.png differ diff --git a/res/drawable-xxxhdpi/ic_grabber.png b/res/drawable-xxxhdpi/ic_grabber.png new file mode 100644 index 00000000..ba7706f2 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_grabber.png differ diff --git a/res/drawable/fab_background.xml b/res/drawable/fab_background.xml new file mode 100644 index 00000000..50e5ff80 --- /dev/null +++ b/res/drawable/fab_background.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/floating_action_button.xml b/res/drawable/floating_action_button.xml new file mode 100644 index 00000000..770d8ba9 --- /dev/null +++ b/res/drawable/floating_action_button.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/res/drawable/ic_settings_weather.xml b/res/drawable/ic_settings_weather.xml deleted file mode 100644 index b4821321..00000000 --- a/res/drawable/ic_settings_weather.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - diff --git a/res/layout/action_list_view_item.xml b/res/layout/action_list_view_item.xml new file mode 100644 index 00000000..032ea0b8 --- /dev/null +++ b/res/layout/action_list_view_item.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + diff --git a/res/layout/action_list_view_main.xml b/res/layout/action_list_view_main.xml new file mode 100644 index 00000000..097e1ced --- /dev/null +++ b/res/layout/action_list_view_main.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/res/layout/fab.xml b/res/layout/fab.xml new file mode 100644 index 00000000..9c41f3e2 --- /dev/null +++ b/res/layout/fab.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/res/values/liquid_arrays.xml b/res/values/liquid_arrays.xml index fcfdd113..9aad8c2f 100644 --- a/res/values/liquid_arrays.xml +++ b/res/values/liquid_arrays.xml @@ -6,7 +6,7 @@ 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 + 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, @@ -41,6 +41,161 @@ 30000 + + + @string/toast_none_animation + @string/toast_default_animation + @string/toast_fade_animation + @string/toast_SlideRight_animation + @string/toast_SlideLeft_animation + @string/toast_Xylon_animation + @string/toast_Toko_animation + @string/toast_Tn_animation + @string/toast_Honami_animation + @string/toast_FastFade_animation + @string/toast_GrowFade_animation + @string/toast_GrowFadeCenter_animation + @string/toast_GrowFadeBottom_animation + @string/toast_Translucent_animation + @string/toast_SlideLeftRight_animation + @string/toast_SlideRightLeft_animation + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + + + @string/listview_off + @string/listview_accelerate_interpolator + @string/listview_decelerate_interpolator + @string/listview_accelerate_decelerate_interpolator + @string/listview_anticipate_interpolator + @string/listview_overshoot_interpolator + @string/listview_anticipate_overshoot_interpolator + @string/listview_bounce_interpolator + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + + @string/qs_tile_animation_style_off + @string/qs_tile_animation_style_flip + @string/qs_tile_animation_style_rotate + + + + 0 + 1 + 2 + + + + + @string/qs_tile_animation_duration_low + @string/qs_tile_animation_duration_default + @string/qs_tile_animation_duration_fast + + + + 2500 + 2000 + 1500 + + + + + @string/qs_tile_animation_interpolator_linearInterpolator + @string/qs_tile_animation_interpolator_accelerateInterpolator + @string/qs_tile_animation_interpolator_decelerateInterpolator + @string/qs_tile_animation_interpolator_accelerateDecelerateInterpolator + @string/qs_tile_animation_interpolator_bounceInterpolator + @string/qs_tile_animation_interpolator_overshootInterpolator + @string/qs_tile_animation_interpolator_anticipateInterpolator + @string/qs_tile_animation_interpolator_anticipateOvershootInterpolator + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + + @string/pref_scrollingcache_force_enable + @string/pref_scrollingcache_default_enable + @string/pref_scrollingcache_default_disable + @string/pref_scrollingcache_force_disable + + + + 0 + 1 + 2 + 3 + + + + + @string/listview_off + @string/listview_wave_left + @string/listview_wave_right + @string/listview_scale + @string/listview_alpha + @string/listview_stack_top + @string/listview_stack_bottom + @string/listview_unfold + @string/listview_fold + @string/listview_translate_left + @string/listview_translate_right + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + @string/action_entry_default_action @@ -348,161 +503,6 @@ 3 - - - @string/toast_none_animation - @string/toast_default_animation - @string/toast_fade_animation - @string/toast_SlideRight_animation - @string/toast_SlideLeft_animation - @string/toast_Xylon_animation - @string/toast_Toko_animation - @string/toast_Tn_animation - @string/toast_Honami_animation - @string/toast_FastFade_animation - @string/toast_GrowFade_animation - @string/toast_GrowFadeCenter_animation - @string/toast_GrowFadeBottom_animation - @string/toast_Translucent_animation - @string/toast_SlideLeftRight_animation - @string/toast_SlideRightLeft_animation - - - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - - - - - @string/listview_off - @string/listview_accelerate_interpolator - @string/listview_decelerate_interpolator - @string/listview_accelerate_decelerate_interpolator - @string/listview_anticipate_interpolator - @string/listview_overshoot_interpolator - @string/listview_anticipate_overshoot_interpolator - @string/listview_bounce_interpolator - - - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - - - - - @string/listview_off - @string/listview_wave_left - @string/listview_wave_right - @string/listview_scale - @string/listview_alpha - @string/listview_stack_top - @string/listview_stack_bottom - @string/listview_unfold - @string/listview_fold - @string/listview_translate_left - @string/listview_translate_right - - - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - - - - - @string/pref_scrollingcache_force_enable - @string/pref_scrollingcache_default_enable - @string/pref_scrollingcache_default_disable - @string/pref_scrollingcache_force_disable - - - - 0 - 1 - 2 - 3 - - - - - @string/qs_tile_animation_style_off - @string/qs_tile_animation_style_flip - @string/qs_tile_animation_style_rotate - - - - 0 - 1 - 2 - - - - - @string/qs_tile_animation_duration_low - @string/qs_tile_animation_duration_default - @string/qs_tile_animation_duration_fast - - - - 2500 - 2000 - 1500 - - - - - @string/qs_tile_animation_interpolator_linearInterpolator - @string/qs_tile_animation_interpolator_accelerateInterpolator - @string/qs_tile_animation_interpolator_decelerateInterpolator - @string/qs_tile_animation_interpolator_accelerateDecelerateInterpolator - @string/qs_tile_animation_interpolator_bounceInterpolator - @string/qs_tile_animation_interpolator_overshootInterpolator - @string/qs_tile_animation_interpolator_anticipateInterpolator - @string/qs_tile_animation_interpolator_anticipateOvershootInterpolator - - - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - - @string/media_scanner_on_boot_enabled @@ -747,25 +747,6 @@ @string/since_boot - - - @string/status_bar_weather_hidden - @string/status_bar_temperature_image_show_scale - @string/status_bar_temperature_image_hide_scale - @string/status_bar_temperature_show_scale_hide_image - @string/status_bar_temperature_hide_scale_hide_image - @string/status_bar_show_image - - - - 0 - 1 - 2 - 3 - 4 - 5 - - @string/quick_pulldown_off @@ -960,41 +941,41 @@ - @string/pie_auto - @string/pie_white - @string/pie_dark + @string/pie_auto + @string/pie_white + @string/pie_dark - 0 - 1 - 2 + 0 + 1 + 2 - @string/pie_battery_text - @string/pie_battery_circle - @string/pie_battery_both + @string/pie_battery_text + @string/pie_battery_circle + @string/pie_battery_both - 0 - 1 - 2 + 0 + 1 + 2 - @string/pie_status_none - @string/pie_status_wifi - @string/pie_status_mobile - @string/pie_status_both + @string/pie_status_none + @string/pie_status_wifi + @string/pie_status_mobile + @string/pie_status_both - 3 - 1 - 2 - 0 + 3 + 1 + 2 + 0 @@ -1069,17 +1050,6 @@ 5 - - - @string/pulse_render_mode_fading_bars - @string/pulse_render_mode_solid_lines - - - - 0 - 1 - - @string/ticker_animation_mode_alpha_fade @@ -1104,4 +1074,415 @@ 0 + + + @string/power_menu_animation_aosp + @string/power_menu_animation_bottom + @string/power_menu_animation_top + @string/power_menu_animation_fly + @string/power_menu_animation_turn + @string/power_menu_animation_slide + @string/power_menu_animation_xylon + @string/power_menu_animation_card + @string/power_menu_animation_translucent_top + @string/power_menu_animation_translucent_bottom + @string/power_menu_animation_rotate + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + + + + @string/shortcuts_icon_default + @string/shortcuts_icon_presets + @string/shortcuts_icon_custom + + + + @string/shortcuts_icon_picker_alarm + @string/shortcuts_icon_picker_andy + @string/shortcuts_icon_picker_battery + @string/shortcuts_icon_picker_browser + @string/shortcuts_icon_picker_calendar + @string/shortcuts_icon_picker_camera + @string/shortcuts_icon_picker_cloud + @string/shortcuts_icon_picker_contact + @string/shortcuts_icon_picker_directdial + @string/shortcuts_icon_picker_directmessage + @string/shortcuts_icon_picker_drive + @string/shortcuts_icon_picker_dropbox + @string/shortcuts_icon_picker_email + @string/shortcuts_icon_picker_email2 + @string/shortcuts_icon_picker_evernote + @string/shortcuts_icon_picker_facebook + @string/shortcuts_icon_picker_favorite + @string/shortcuts_icon_picker_file_browser + @string/shortcuts_icon_picker_file_browser2 + @string/shortcuts_icon_picker_fitness + @string/shortcuts_icon_picker_gallery + @string/shortcuts_icon_picker_gears + @string/shortcuts_icon_picker_google_small + @string/shortcuts_icon_picker_gplus + @string/shortcuts_icon_picker_gtalk + @string/shortcuts_icon_picker_help + @string/shortcuts_icon_picker_joystick + @string/shortcuts_icon_picker_key + @string/shortcuts_icon_picker_laptop + @string/shortcuts_icon_picker_luggage + @string/shortcuts_icon_picker_maps + @string/shortcuts_icon_picker_market + @string/shortcuts_icon_picker_movie + @string/shortcuts_icon_picker_music + @string/shortcuts_icon_picker_nav + @string/shortcuts_icon_picker_notes + @string/shortcuts_icon_picker_package + @string/shortcuts_icon_picker_phone + @string/shortcuts_icon_picker_pinterest + @string/shortcuts_icon_picker_play + @string/shortcuts_icon_picker_pocket + @string/shortcuts_icon_picker_quicksettings + @string/shortcuts_icon_picker_rss + @string/shortcuts_icon_picker_sdcard + @string/shortcuts_icon_picker_search + @string/shortcuts_icon_picker_sms + @string/shortcuts_icon_picker_tasks + @string/shortcuts_icon_picker_terminal + @string/shortcuts_icon_picker_transit + @string/shortcuts_icon_picker_tv + @string/shortcuts_icon_picker_twitter + @string/shortcuts_icon_picker_unlock + @string/airplay + @string/android + @string/badoo + @string/battery + @string/bluetooth + @string/bookmarks + @string/browser + @string/browser2 + @string/calculator + @string/calendar + @string/calls + @string/camera + @string/chrome + @string/clock + @string/cnn + @string/deezer + @string/deezer2 + @string/dolphin2 + @string/downloads + @string/dropbox + @string/ebay + @string/email + @string/evernote + @string/explorer + @string/falcon + @string/fb + @string/flipboard + @string/foursquare + @string/firefox + @string/googlebuzz + @string/googledocs + @string/googlewallet + @string/line + @string/meebo + @string/mic + @string/newyorktimes + @string/rootexplorer + @string/sim + @string/teamviewer + @string/totalcommander + @string/weather + @string/golocker + @string/googlecurrents + @string/googletalk + @string/gosms + @string/maps2 + @string/memorycleaner + @string/navigation + @string/pulse + @string/shazam + @string/swiftkey + @string/tmobile + @string/trash + @string/games + @string/gmail + @string/docs + @string/googledrive + @string/googleearth + @string/googlehangouts + @string/googlekeep + @string/googleplay + @string/googletranslate + @string/google + @string/googleplus + @string/imdb + @string/instagram + @string/lastfm + @string/maps + @string/messages + @string/music + @string/mxplayer + @string/notepad + @string/opera + @string/paint + @string/paypal + @string/phone + @string/picasa + @string/pictures2 + @string/player_pro + @string/playstation + @string/reddit + @string/settings + @string/skype + @string/soundscloud + @string/spotifiy + @string/tapatalk + @string/tunein + @string/twitter + @string/viber + @string/videocamera + @string/whatsapp + @string/wifi + @string/xda + @string/yahoo + @string/youtube + @string/zedge + + + + @*android:drawable/ic_shortcut_alarm + @*android:drawable/ic_shortcut_andy + @*android:drawable/ic_shortcut_battery + @*android:drawable/ic_shortcut_browser + @*android:drawable/ic_shortcut_calendar + @*android:drawable/ic_shortcut_camera + @*android:drawable/ic_shortcut_cloud + @*android:drawable/ic_shortcut_contact + @*android:drawable/ic_shortcut_directdial + @*android:drawable/ic_shortcut_directmessage + @*android:drawable/ic_shortcut_drive + @*android:drawable/ic_shortcut_dropbox + @*android:drawable/ic_shortcut_email + @*android:drawable/ic_shortcut_email2 + @*android:drawable/ic_shortcut_evernote + @*android:drawable/ic_shortcut_facebook + @*android:drawable/ic_shortcut_favorite + @*android:drawable/ic_shortcut_file_browser + @*android:drawable/ic_shortcut_file_browser2 + @*android:drawable/ic_shortcut_fitness + @*android:drawable/ic_shortcut_gallery + @*android:drawable/ic_shortcut_gear + @*android:drawable/ic_shortcut_google_small + @*android:drawable/ic_shortcut_gplus + @*android:drawable/ic_shortcut_gtalk + @*android:drawable/ic_shortcut_help + @*android:drawable/ic_shortcut_joystick + @*android:drawable/ic_shortcut_key + @*android:drawable/ic_shortcut_laptop + @*android:drawable/ic_shortcut_luggage + @*android:drawable/ic_shortcut_maps + @*android:drawable/ic_shortcut_market + @*android:drawable/ic_shortcut_movie + @*android:drawable/ic_shortcut_music + @*android:drawable/ic_shortcut_nav + @*android:drawable/ic_shortcut_notes + @*android:drawable/ic_shortcut_package + @*android:drawable/ic_shortcut_phone + @*android:drawable/ic_shortcut_pinterest + @*android:drawable/ic_shortcut_play + @*android:drawable/ic_shortcut_pocket + @*android:drawable/ic_shortcut_quicksettings + @*android:drawable/ic_shortcut_rss + @*android:drawable/ic_shortcut_sdcard + @*android:drawable/ic_shortcut_search + @*android:drawable/ic_shortcut_sms + @*android:drawable/ic_shortcut_tasks + @*android:drawable/ic_shortcut_terminal + @*android:drawable/ic_shortcut_transit + @*android:drawable/ic_shortcut_tv + @*android:drawable/ic_shortcut_twitter + @*android:drawable/ic_shortcut_unlock + @*android:drawable/airplay + @*android:drawable/android + @*android:drawable/badoo + @*android:drawable/battery + @*android:drawable/bluetooth + @*android:drawable/bookmarks + @*android:drawable/browser + @*android:drawable/browser2 + @*android:drawable/calculator + @*android:drawable/calendar + @*android:drawable/calls + @*android:drawable/camera + @*android:drawable/chrome + @*android:drawable/clock + @*android:drawable/cnn + @*android:drawable/deezer + @*android:drawable/deezer2 + @*android:drawable/dolphin2 + @*android:drawable/downloads + @*android:drawable/dropbox + @*android:drawable/ebay + @*android:drawable/email + @*android:drawable/evernote + @*android:drawable/explorer + @*android:drawable/falcon + @*android:drawable/fb + @*android:drawable/flipboard + @*android:drawable/foursquare + @*android:drawable/firefox + @*android:drawable/googlebuzz + @*android:drawable/googledocs + @*android:drawable/googlewallet + @*android:drawable/line + @*android:drawable/meebo + @*android:drawable/mic + @*android:drawable/newyorktimes + @*android:drawable/rootexplorer + @*android:drawable/sim + @*android:drawable/teamviewer + @*android:drawable/totalcommander + @*android:drawable/weather + @*android:drawable/golocker + @*android:drawable/googlecurrents + @*android:drawable/googletalk + @*android:drawable/gosms + @*android:drawable/maps2 + @*android:drawable/memorycleaner + @*android:drawable/navigation + @*android:drawable/pulse + @*android:drawable/shazam + @*android:drawable/swiftkey + @*android:drawable/tmobile + @*android:drawable/trash + @*android:drawable/games + @*android:drawable/gmail + @*android:drawable/docs + @*android:drawable/googledrive + @*android:drawable/googleearth + @*android:drawable/googlehangouts + @*android:drawable/googlekeep + @*android:drawable/googleplay + @*android:drawable/googletranslate + @*android:drawable/google + @*android:drawable/googleplus + @*android:drawable/imdb + @*android:drawable/instagram + @*android:drawable/lastfm + @*android:drawable/maps + @*android:drawable/messages + @*android:drawable/music + @*android:drawable/mxplayer + @*android:drawable/notepad + @*android:drawable/opera + @*android:drawable/paint + @*android:drawable/paypal + @*android:drawable/phone + @*android:drawable/picasa + @*android:drawable/pictures2 + @*android:drawable/player_pro + @*android:drawable/playstation + @*android:drawable/reddit + @*android:drawable/settings + @*android:drawable/skype + @*android:drawable/soundscloud + @*android:drawable/spotifiy + @*android:drawable/tapatalk + @*android:drawable/tunein + @*android:drawable/twitter + @*android:drawable/viber + @*android:drawable/videocamera + @*android:drawable/whatsapp + @*android:drawable/wifi + @*android:drawable/xda + @*android:drawable/yahoo + @*android:drawable/youtube + @*android:drawable/zedge + + + + @string/shortcut_action_back- + @string/shortcut_action_app- + @string/shortcut_action_home- + @string/shortcut_action_menu + @string/shortcut_action_ime + @string/shortcut_action_power_menu + @string/shortcut_action_recent + @string/shortcut_action_power + @string/shortcut_action_search + @string/shortcut_action_none + + + + **back** + **app** + **home** + **menu_big** + **ime** + **power_menu** + **recents** + **power** + **search** + **null** + + + + @string/shortcut_action_app + @string/shortcut_action_camera + @string/shortcut_action_media_next + @string/shortcut_action_media_play_pause + @string/shortcut_action_media_previous + @string/shortcut_action_ring_silent + @string/shortcut_action_ring_vib + @string/shortcut_action_ring_vib_silent + @string/shortcut_action_wake_device + @string/shortcut_action_none + + + + **app** + **camera** + **media_next** + **media_play_pause** + **media_previous** + **ring_silent** + **ring_vib** + **ring_vib_silent** + **wake_device** + **null** + + + + @string/lockscreen_shortcuts_single_tap + @string/lockscreen_shortcuts_double_tap + @string/lockscreen_shortcuts_longpress + + + + 0 + 1 + 2 + + + + + @string/weather_unit_metric + @string/weather_unit_imperial + + + 0 + 1 + diff --git a/res/values/liquid_attrs.xml b/res/values/liquid_attrs.xml index 5c3f2c95..a20d6db0 100644 --- a/res/values/liquid_attrs.xml +++ b/res/values/liquid_attrs.xml @@ -15,6 +15,7 @@ limitations under the License. --> + @@ -29,7 +30,7 @@ - + @@ -41,5 +42,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/liquid_colors.xml b/res/values/liquid_colors.xml index 3071bf38..be9d1a44 100644 --- a/res/values/liquid_colors.xml +++ b/res/values/liquid_colors.xml @@ -20,4 +20,13 @@ #80ffffff @*android:color/accent_material_dark + + #ffffff + + #80000000 + #ff1b1f23 + + + #1fffffff + ?android:attr/colorAccent diff --git a/res/values/liquid_dimens.xml b/res/values/liquid_dimens.xml index 4d20a86b..eef4a948 100644 --- a/res/values/liquid_dimens.xml +++ b/res/values/liquid_dimens.xml @@ -29,4 +29,16 @@ 8dip 64dip + 2dp + + + 56dp + 16dp + 24dp + 8dp + 6dp + + + 15dp + 20dp diff --git a/res/xml/pulse_blacklist.xml b/res/values/liquid_ids.xml similarity index 54% rename from res/xml/pulse_blacklist.xml rename to res/values/liquid_ids.xml index ccd3d3fe..bac4ba8f 100644 --- a/res/xml/pulse_blacklist.xml +++ b/res/values/liquid_ids.xml @@ -14,17 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - + - + - - - - + diff --git a/res/values/liquid_strings.xml b/res/values/liquid_strings.xml index 33c83b7b..3b6f9884 100644 --- a/res/values/liquid_strings.xml +++ b/res/values/liquid_strings.xml @@ -36,7 +36,6 @@ Recents panel Status bar Volume rocker - Weather Clock @@ -53,9 +52,11 @@ Stock recents Hide apps Ticker - Widget Styles + Widget styles Music Traffic + Power menu + Notifications OK @@ -92,6 +93,7 @@ Screenshot + Screenrecord Power Restart Advanced restart @@ -668,19 +670,6 @@ Only in DND mode Always - - Weather service - System service settings - Header view weather - Enable notification panel header weather display - Condition icon pack - Note\u003a you can install new icon packs from play store by searching for \"Chronus icons\" - - - Lockscreen weather - Show current weather condition and temperature on lockscreen - This visibility configuration also applies to the ambient display - Battery bar Battery bar location @@ -857,17 +846,6 @@ Swap keys Swap back and recents hardware keys - - Statusbar weather - Disabled - Statusbar Weather - Disabled - Temp with image and scale - Temp with image - Temp with scale - Temp - Image - Pocket detection Block screen and button inputs when device is in pocket @@ -1191,47 +1169,8 @@ Could not load %s. Make sure you have storage available. - - Pulse does not work with some audio sources when listening on device speaker - Pulse - Navigation bar audio graphic equalizer - Allow Pulse - Audio graphic equalizer on the navigation bar - Render mode - %1$s - Fading blocks - Solid lines - Pulse color - Used also as fallback for automatic color - Turn on Lava Lamp - Pulse animates a smooth lava lamp style color blend - Advanced - Fading blocks mode settings - Sanity level - Lava Lamp speed - Solid lines count - Solid lines count - Solid lines opacity - Solid lines mode settings - Smartbar buttons opacity on Pulse - Automatic Pulse color - Sync color with album arts. Overrides Lava Lamp and lines opacity. - - - Bar width - Bar spacing - Block size - Block spacing - Apps blacklist - Apps to be skipped for Pulse - Blacklist - Apps blacklist - Apps to not show in the panel - Add app - Tap here to add, tap on added app to remove it - - Show notification count + Notification count Display the number of pending notifications @@ -1265,4 +1204,261 @@ Privacy guard + + Power menu animation + Change the enter/exit animation of the power menu + Select power menu animation + AOSP (default) + Bottom + Top + Fly + Slide + Turn + Glow + 3D + Translucent (top) + Translucent (bottom) + Rotate + + + Breathing SMS + Will give your sms notifications a breathing effect in the status bar + Breathing missed call + Will give your missed call notifications a breathing effect in the statusbar + Breathing voicemail + Will give your voicemail notifications a breathing effect in the statusbar + + + Immersive mode messages + This will hide all Immersive mode messages + + + None + Choose action: + Choose new action: + Reset + Add + Reset all entries to default? + Maximum entries size has been reached + Warning + It is not possible to delete last entry + Disabled\nAdd an entry to enable this feature + The chosen app did not return a valid icon or does not support image cropping. Use another app. + The chosen action cannot be added twice + shortcut + button + app + , while selecting the icon will bring up customization options + To add a %1$s, select the Add icon. Once a %1$s is added, selecting the row will change the target%2$s\n\nTo remove a %1$s, swipe the row horizontally left or right. Re-order a %1$s in the list by vertically dragging the anchor on the left.\n\nTo clear or reset to defaults, select the Reset icon. + Removing every %1$s in the list will disable the feature entirely. + Removing every %1$s in the list will disable the second pie layer entirely. + Choose new action: + Reassign action: + Choose action: + Applications + Activities + Select application: + Select activity: + Choose icon type: + Default + System icons + Select system icon: + Gallery + + + Lockscreen shortcuts + View or change custom lock screen shortcuts + Shortcut click type + Applications + Select custom application + Select icon type: + Default + System icons + Gallery + Choose icon + Alarm + Andy + Battery + Browser + Calendar + Camera + Cloud + Contact + Direct Dial + Direct Message + Drive + Dropbox + Email + Email Alternate + Evernote + Facebook + Favorite + File Browser + File Browser Alternate + Fitness + Gallery + Gears + Google + Google Plus + Hangouts + Help + Joystick + Keys/Accounts + Laptop + Notes + Travel + Maps + Market + Movies + Music + Navigation + Phone + Package + Pinterest + Play + Pocket + Quick Settings + RSS Feed + SDCard + Search + Messaging + Tasks + Terminal + Transit + TV + Twitter + Unlock + Custom app + Home + Back + Menu + Open IME Switcher + Power menu + Recents + Screen Off + Search in app + Ring/Vib + Ring/Silent + Ring/Vib/Silent + Camera + Music - next track + Music - play/pause + Music - previous track + Wake up device + Longpress: + Choose longpress action: + Airplay + Android + Badoo + Battery + Bluetooth + Bookmarks + Browser + Browser2 + Calculator + Calendar + Calls + Camera + Chrome + Clock + Cnn + Deezer2 + Deezer + Dolphin + Downloads + Dropbox + Ebay + Email + Evernote + Explorer + Falcon + Facebook + Flipboard + Foursquare + Firefox + Google buzz + Google docs + Google wallet + Line + Mic + Newyork times + Root explorer + Sim + Teamviewer + Total commander + Weather + Go locker + Google currents + Google talk + Go sms + Meebo + Maps2 + Memory cleaner + Navigation + Pulse + Shazam + Swiftkey + Tmobile + Trash + Games + Gmail + Docs + Google Drive + Google earth + Google hangouts + Google keep + Google play + Google translate + Google + Google plus + imdb + instagram + Lastfm + Maps + Messages + Music + Mxplayer + Direct Message + Opera + Paint + Paypal + Phone + Picasa + Pictures2 + Player pro + Play station + Reddit + Settings + Skype + Sounds cloud + Spotifiy + Tapatalk + Tunein + Twitter + Viber + Video camera + Whatsapp + Wifi + Xda + Yahoo + Youtube + Zedge + Click + Double tap + Longpress + Choose longpress action: + Help + + + Volume dialog timeout + + + Control ring volume + Keys control media slider by default. Enable to switch to ring tone slider. + + + Weather + Temperature unit + Celsius + Fahrenheit diff --git a/res/values/liquid_styles.xml b/res/values/liquid_styles.xml index 2769a6d7..c9ea834b 100644 --- a/res/values/liquid_styles.xml +++ b/res/values/liquid_styles.xml @@ -28,4 +28,23 @@ false + + + + diff --git a/res/values/seekbar_preference_styles.xml b/res/values/seekbar_preference_styles.xml index 2a06f396..4266e20f 100644 --- a/res/values/seekbar_preference_styles.xml +++ b/res/values/seekbar_preference_styles.xml @@ -29,4 +29,28 @@ false + + + + + + diff --git a/res/xml/ambient_settings.xml b/res/xml/ambient_settings.xml index d76e52d4..48b60ff9 100644 --- a/res/xml/ambient_settings.xml +++ b/res/xml/ambient_settings.xml @@ -32,6 +32,24 @@ + + + + + + + + diff --git a/res/xml/animation_settings.xml b/res/xml/animation_settings.xml index 1492d9fb..4bc9666f 100644 --- a/res/xml/animation_settings.xml +++ b/res/xml/animation_settings.xml @@ -135,6 +135,21 @@ + + + + + + diff --git a/res/xml/liquid_settings.xml b/res/xml/liquid_settings.xml index b1cee97f..7925bf54 100644 --- a/res/xml/liquid_settings.xml +++ b/res/xml/liquid_settings.xml @@ -121,11 +121,4 @@ android:icon="@drawable/ic_settings_statusbar" android:fragment="com.liquid.liquidlounge.fragments.StatusBarSettings" /> - - - diff --git a/res/xml/lockscreen_settings.xml b/res/xml/lockscreen_settings.xml index 2f71200f..fd1a273d 100644 --- a/res/xml/lockscreen_settings.xml +++ b/res/xml/lockscreen_settings.xml @@ -19,6 +19,18 @@ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings" android:title="@string/lockscreen_title"> + + + + + + + + diff --git a/res/xml/lockscreen_shortcuts.xml b/res/xml/lockscreen_shortcuts.xml new file mode 100644 index 00000000..36f1a6c0 --- /dev/null +++ b/res/xml/lockscreen_shortcuts.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/res/xml/navigation_settings.xml b/res/xml/navigation_settings.xml index b679d007..156a4dd1 100644 --- a/res/xml/navigation_settings.xml +++ b/res/xml/navigation_settings.xml @@ -45,10 +45,4 @@ android:dependency="navigation_bar_show" android:defaultValue="false" /> - - diff --git a/res/xml/notifications_settings.xml b/res/xml/notifications_settings.xml index 3176d189..03d5fb33 100644 --- a/res/xml/notifications_settings.xml +++ b/res/xml/notifications_settings.xml @@ -60,6 +60,12 @@ android:summary="@string/force_expanded_notifications_summary" android:defaultValue="false" /> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/statusbar_settings.xml b/res/xml/statusbar_settings.xml index aab2e1e7..2a9e99fe 100644 --- a/res/xml/statusbar_settings.xml +++ b/res/xml/statusbar_settings.xml @@ -55,12 +55,6 @@ android:entries="@array/status_bar_battery_percentage_entries" android:entryValues="@array/status_bar_battery_percentage_values" /> - - + + + + + + + + + + + + diff --git a/res/xml/volume_settings.xml b/res/xml/volume_settings.xml index 971706f9..0f87e324 100644 --- a/res/xml/volume_settings.xml +++ b/res/xml/volume_settings.xml @@ -17,6 +17,7 @@ @@ -26,6 +27,22 @@ android:summary="@string/volume_steps_summary" android:fragment="com.liquid.liquidlounge.fragments.VolumeSteps" /> + + + + - - - - - - - - - - - - - - diff --git a/src/com/liquid/liquidlounge/fragments/AnimationSettings.java b/src/com/liquid/liquidlounge/fragments/AnimationSettings.java index 7a1c379a..5a57dca5 100644 --- a/src/com/liquid/liquidlounge/fragments/AnimationSettings.java +++ b/src/com/liquid/liquidlounge/fragments/AnimationSettings.java @@ -75,6 +75,7 @@ public class AnimationSettings extends SettingsPreferenceFragment private static final String PREF_TILE_ANIM_STYLE = "qs_tile_animation_style"; private static final String PREF_TILE_ANIM_DURATION = "qs_tile_animation_duration"; private static final String PREF_TILE_ANIM_INTERPOLATOR = "qs_tile_animation_interpolator"; + private static final String POWER_MENU_ANIMATIONS = "power_menu_animations"; private ListPreference mScreenOffAnimation; private ListPreference mToastAnimation; @@ -96,6 +97,7 @@ public class AnimationSettings extends SettingsPreferenceFragment ListPreference mWallpaperClose; ListPreference mWallpaperIntraOpen; ListPreference mWallpaperIntraClose; + private ListPreference mPowerMenuAnimations; private int[] mAnimations; private String[] mAnimationsStrings; @@ -259,6 +261,12 @@ public void onCreate(Bundle savedInstanceState) { mWallpaperIntraClose.setEntryValues(mAnimationsNum); mWallpaperIntraClose.setOnPreferenceChangeListener(this); + mPowerMenuAnimations = (ListPreference) findPreference(POWER_MENU_ANIMATIONS); + mPowerMenuAnimations.setValue(String.valueOf(Settings.System.getInt( + getContentResolver(), Settings.System.POWER_MENU_ANIMATIONS, 0))); + mPowerMenuAnimations.setSummary(mPowerMenuAnimations.getEntry()); + mPowerMenuAnimations.setOnPreferenceChangeListener(this); + if (mToast != null) { mToast.cancel(); mToast = null; @@ -399,6 +407,12 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { Settings.System.ACTIVITY_ANIMATION_CONTROLS[10], val); preference.setSummary(getProperSummary(preference)); return true; + } else if (preference == mPowerMenuAnimations) { + Settings.System.putInt(getContentResolver(), Settings.System.POWER_MENU_ANIMATIONS, + Integer.valueOf((String) newValue)); + mPowerMenuAnimations.setValue(String.valueOf(newValue)); + mPowerMenuAnimations.setSummary(mPowerMenuAnimations.getEntry()); + return true; } return false; } diff --git a/src/com/liquid/liquidlounge/fragments/LockScreenSettings.java b/src/com/liquid/liquidlounge/fragments/LockScreenSettings.java index e41ae913..25834e97 100644 --- a/src/com/liquid/liquidlounge/fragments/LockScreenSettings.java +++ b/src/com/liquid/liquidlounge/fragments/LockScreenSettings.java @@ -35,6 +35,7 @@ import net.margaritov.preference.colorpicker.ColorPickerPreference; import android.provider.Settings; +import com.android.internal.util.ambient.weather.WeatherClient; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; @@ -53,6 +54,8 @@ public class LockScreenSettings extends SettingsPreferenceFragment private static final String CLOCK_FONT_SIZE = "lockclock_font_size"; private static final String DATE_FONT_SIZE = "lockdate_font_size"; + private static final String WEATHER_LS_CAT = "lockscreen_weather"; + private FingerprintManager mFingerprintManager; private SwitchPreference mFingerprintVib; private SwitchPreference mFaceUnlock; @@ -126,6 +129,13 @@ public void onCreate(Bundle icicle) { getContentResolver(), Settings.System.LOCK_DATE_FONTS, 0))); mLockDateFonts.setSummary(mLockDateFonts.getEntry()); mLockDateFonts.setOnPreferenceChangeListener(this); + + final PreferenceCategory weatherCategory = (PreferenceCategory) prefScreen + .findPreference(WEATHER_LS_CAT); + + if (!WeatherClient.isAvailable(getContext())) { + prefScreen.removePreference(weatherCategory); + } } public boolean onPreferenceChange(Preference preference, Object newValue) { diff --git a/src/com/liquid/liquidlounge/fragments/LockScreenShortcuts.java b/src/com/liquid/liquidlounge/fragments/LockScreenShortcuts.java new file mode 100644 index 00000000..267be0a5 --- /dev/null +++ b/src/com/liquid/liquidlounge/fragments/LockScreenShortcuts.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 The Liquid Remix 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.liquid.liquidlounge.fragments; + +import com.android.internal.logging.nano.MetricsProto; + +import android.os.Bundle; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +public class LockScreenShortcuts extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static final String PREF_LOCKSCREEN_SHORTCUTS_LAUNCH_TYPE = + "lockscreen_shortcuts_launch_type"; + + private ListPreference mLockscreenShortcutsLaunchType; + + @Override + public int getMetricsCategory() { + return MetricsEvent.LIQUID; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.lockscreen_shortcuts); + + PreferenceScreen prefSet = getPreferenceScreen(); + + mLockscreenShortcutsLaunchType = (ListPreference) findPreference( + PREF_LOCKSCREEN_SHORTCUTS_LAUNCH_TYPE); + mLockscreenShortcutsLaunchType.setOnPreferenceChangeListener(this); + + setHasOptionsMenu(false); + } + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + final View view = super.onCreateView(inflater, container, savedInstanceState); + final ListView list = (ListView) view.findViewById(android.R.id.list); + // our container already takes care of the padding + if (list != null) { + int paddingTop = list.getPaddingTop(); + int paddingBottom = list.getPaddingBottom(); + list.setPadding(0, paddingTop, 0, paddingBottom); + } + return view; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mLockscreenShortcutsLaunchType) { + Settings.System.putInt(getContentResolver(), + Settings.System.LOCKSCREEN_SHORTCUTS_LONGPRESS, + (Boolean) newValue ? 1 : 0); + Integer.valueOf((String) newValue); + } + return true; + } +} diff --git a/src/com/liquid/liquidlounge/fragments/PulseBlacklist.java b/src/com/liquid/liquidlounge/fragments/PulseBlacklist.java deleted file mode 100644 index 2c07cc4e..00000000 --- a/src/com/liquid/liquidlounge/fragments/PulseBlacklist.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2018 The Liquid Remix 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.liquid.liquidlounge.fragments; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceCategory; -import android.support.v7.preference.PreferenceGroup; -import android.support.v7.preference.PreferenceScreen; -import android.text.TextUtils; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; - -import com.liquid.liquidlounge.preferences.PackageListAdapter; -import com.liquid.liquidlounge.preferences.PackageListAdapter.PackageItem; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.List; - -public class PulseBlacklist extends SettingsPreferenceFragment implements - Preference.OnPreferenceClickListener { - - private static final String TAG = "PulseBlacklist"; - - private static final int DIALOG_BLACKLIST_APPS = 1; - - private PackageListAdapter mPackageAdapter; - private PackageManager mPackageManager; - private PreferenceGroup mBlacklistPrefList; - private Preference mAddBlacklistPref; - private String mBlacklistPackageList; - private Map mBlacklistPackages; - - private AlertDialog mDialog; - private ListView mListView; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pulse_blacklist); - initializeAllPreferences(); - } - - @Override - public Dialog onCreateDialog(int dialogId) { - switch (dialogId) { - case DIALOG_BLACKLIST_APPS: { - Dialog dialog; - AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity()); - final ListView list = new ListView(getActivity()); - list.setAdapter(mPackageAdapter); - alertDialog.setTitle(R.string.profile_choose_app); - alertDialog.setView(list); - dialog = alertDialog.create(); - list.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - // Add empty application definition, the user will be able to edit it later - PackageItem info = (PackageItem) parent.getItemAtPosition(position); - addCustomApplicationPref(info.packageName, mBlacklistPackages); - dialog.cancel(); - } - }); - return dialog; - } - } - return super.onCreateDialog(dialogId); - } - - @Override - public int getDialogMetricsCategory(int dialogId) { - switch (dialogId) { - case DIALOG_BLACKLIST_APPS: - return MetricsEvent.LIQUID; - default: - return 0; - } - } - - @Override - public void onResume() { - super.onResume(); - refreshCustomApplicationPrefs(); - } - - private void initializeAllPreferences() { - mPackageManager = getPackageManager(); - mPackageAdapter = new PackageListAdapter(getActivity()); - mBlacklistPrefList = (PreferenceGroup) findPreference("blacklist_applications"); - mBlacklistPrefList.setOrderingAsAdded(false); - mBlacklistPackages = new HashMap(); - mAddBlacklistPref = findPreference("add_blacklist_packages"); - mAddBlacklistPref.setOnPreferenceClickListener(this); - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.LIQUID; - } - - @Override - public boolean onPreferenceClick(Preference preference) { - if (preference == mAddBlacklistPref) { - showDialog(DIALOG_BLACKLIST_APPS); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.dialog_delete_title) - .setMessage(R.string.dialog_delete_message) - //.setIconAttribute(android.R.attr.alertDialogIcon) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - removeApplicationPref(preference.getKey(), mBlacklistPackages); - } - }) - .setNegativeButton(android.R.string.cancel, null); - - builder.show(); - } - return true; - } - - /** - * Application class - */ - private static class Package { - public String name; - /** - * Stores all the application values in one call - * @param name - */ - public Package(String name) { - this.name = name; - } - - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(name); - return builder.toString(); - } - - public static Package fromString(String value) { - if (TextUtils.isEmpty(value)) { - return null; - } - - try { - Package item = new Package(value); - return item; - } catch (NumberFormatException e) { - return null; - } - } - - }; - - private void refreshCustomApplicationPrefs() { - if (!parsePackageList()) { - return; - } - - // Add the Application Preferences - if (mBlacklistPrefList != null) { - mBlacklistPrefList.removeAll(); - - for (Package pkg : mBlacklistPackages.values()) { - try { - Preference pref = createPreferenceFromInfo(pkg); - mBlacklistPrefList.addPreference(pref); - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - } - } - - // Keep these at the top - mAddBlacklistPref.setOrder(0); - // Add 'add' options - mBlacklistPrefList.addPreference(mAddBlacklistPref); - } - - private void addCustomApplicationPref(String packageName, Map map) { - Package pkg = map.get(packageName); - if (pkg == null) { - pkg = new Package(packageName); - map.put(packageName, pkg); - savePackageList(false, map); - refreshCustomApplicationPrefs(); - } - } - - private Preference createPreferenceFromInfo(Package pkg) - throws PackageManager.NameNotFoundException { - PackageInfo info = mPackageManager.getPackageInfo(pkg.name, - PackageManager.GET_META_DATA); - Preference pref = - new Preference(getActivity()); - - pref.setKey(pkg.name); - pref.setTitle(info.applicationInfo.loadLabel(mPackageManager)); - pref.setIcon(info.applicationInfo.loadIcon(mPackageManager)); - pref.setPersistent(false); - pref.setOnPreferenceClickListener(this); - return pref; - } - - private void removeApplicationPref(String packageName, Map map) { - if (map.remove(packageName) != null) { - savePackageList(false, map); - refreshCustomApplicationPrefs(); - } - } - - private boolean parsePackageList() { - boolean parsed = false; - final String blacklistString = Settings.System.getString(getContentResolver(), - Settings.System.PULSE_APPS_BLACKLIST); - - if (!TextUtils.equals(mBlacklistPackageList, blacklistString)) { - mBlacklistPackageList = blacklistString; - mBlacklistPackages.clear(); - parseAndAddToMap(blacklistString, mBlacklistPackages); - parsed = true; - } - - return parsed; - } - - private void parseAndAddToMap(String baseString, Map map) { - if (baseString == null) { - return; - } - - final String[] array = TextUtils.split(baseString, "\\|"); - for (String item : array) { - if (TextUtils.isEmpty(item)) { - continue; - } - Package pkg = Package.fromString(item); - map.put(pkg.name, pkg); - } - } - - - private void savePackageList(boolean preferencesUpdated, Map map) { - String setting = Settings.System.PULSE_APPS_BLACKLIST; - - List settings = new ArrayList(); - for (Package app : map.values()) { - settings.add(app.toString()); - } - final String value = TextUtils.join("|", settings); - if (preferencesUpdated) { - mBlacklistPackageList = value; - } - Settings.System.putString(getContentResolver(), - setting, value); - } -} diff --git a/src/com/liquid/liquidlounge/fragments/PulseSettings.java b/src/com/liquid/liquidlounge/fragments/PulseSettings.java deleted file mode 100644 index afed5633..00000000 --- a/src/com/liquid/liquidlounge/fragments/PulseSettings.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2018 The Liquid Remix 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.liquid.liquidlounge.fragments; - -import android.graphics.Color; -import android.os.Bundle; -import android.os.UserHandle; -import android.provider.Settings; -import android.support.v7.preference.PreferenceCategory; -import android.support.v14.preference.SwitchPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.ListPreference; - -import com.android.internal.logging.nano.MetricsProto; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; - -import com.liquid.liquidlounge.preferences.SystemSettingSeekBarPreference; -import net.margaritov.preference.colorpicker.ColorPickerPreference; - -public class PulseSettings extends SettingsPreferenceFragment implements - Preference.OnPreferenceChangeListener { - - private static final String TAG = PulseSettings.class.getSimpleName(); - private static final String CUSTOM_DIMEN = "pulse_custom_dimen"; - private static final String CUSTOM_DIV = "pulse_custom_div"; - private static final String PULSE_BLOCK = "pulse_filled_block_size"; - private static final String EMPTY_BLOCK = "pulse_empty_block_size"; - private static final String FUDGE_FACOR = "pulse_custom_fudge_factor"; - private static final int RENDER_STYLE_FADING_BARS = 0; - private static final int RENDER_STYLE_SOLID_LINES = 1; - private static final String SOLID_FUDGE = "pulse_solid_fudge_factor"; - private static final String SOLID_LAVAMP_SPEED = "lavamp_solid_speed"; - private static final String FADING_LAVAMP_SPEED = "fling_pulse_lavalamp_speed"; - private static final String PULSE_SOLID_UNITS_COUNT = "pulse_solid_units_count"; - private static final String PULSE_SOLID_UNITS_OPACITY = "pulse_solid_units_opacity"; - //private static final String PULSE_CUSTOM_BUTTONS_OPACITY = "pulse_custom_buttons_opacity"; - - SwitchPreference mShowPulse; - ListPreference mRenderMode; - SwitchPreference mAutoColor; - ColorPickerPreference mPulseColor; - SwitchPreference mLavaLampEnabled; - SystemSettingSeekBarPreference mCustomDimen; - SystemSettingSeekBarPreference mCustomDiv; - SystemSettingSeekBarPreference mFilled; - SystemSettingSeekBarPreference mEmpty; - SystemSettingSeekBarPreference mFudge; - SystemSettingSeekBarPreference mSolidFudge; - SystemSettingSeekBarPreference mSolidSpeed; - SystemSettingSeekBarPreference mFadingSpeed; - SystemSettingSeekBarPreference mSolidCount; - SystemSettingSeekBarPreference mSolidOpacity; - //SystemSettingSeekBarPreference mNavButtonsOpacity; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pulse_settings); - - mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.pulse_help_policy_notice_summary); - - mShowPulse = (SwitchPreference) findPreference("eos_fling_show_pulse"); - mShowPulse.setChecked(Settings.System.getIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) == 1); - mShowPulse.setOnPreferenceChangeListener(this); - - int renderMode = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_RENDER_STYLE_URI, RENDER_STYLE_SOLID_LINES, UserHandle.USER_CURRENT); - mRenderMode = (ListPreference) findPreference("pulse_render_mode"); - mRenderMode.setValue(String.valueOf(renderMode)); - mRenderMode.setOnPreferenceChangeListener(this); - - mAutoColor = (SwitchPreference) findPreference("pulse_auto_color"); - mAutoColor.setChecked(Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_AUTO_COLOR, 0, UserHandle.USER_CURRENT) == 1); - mAutoColor.setOnPreferenceChangeListener(this); - - PreferenceCategory fadingBarsCat = (PreferenceCategory)findPreference("pulse_fading_bars_category"); - fadingBarsCat.setEnabled(renderMode == RENDER_STYLE_FADING_BARS); - - PreferenceCategory solidBarsCat = (PreferenceCategory) findPreference("pulse_2"); - solidBarsCat.setEnabled(renderMode == RENDER_STYLE_SOLID_LINES); - - int pulseColor = Settings.System.getIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_COLOR, Color.WHITE, UserHandle.USER_CURRENT); - mPulseColor = (ColorPickerPreference) findPreference("eos_fling_pulse_color"); - mPulseColor.setNewPreviewColor(pulseColor); - mPulseColor.setOnPreferenceChangeListener(this); - - mLavaLampEnabled = (SwitchPreference) findPreference("eos_fling_lavalamp"); - mLavaLampEnabled.setChecked(Settings.System.getIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_LAVALAMP_ENABLED, 1, UserHandle.USER_CURRENT) == 1); - mLavaLampEnabled.setOnPreferenceChangeListener(this); - - int customdimen = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_DIMEN, 14, UserHandle.USER_CURRENT); - mCustomDimen = (SystemSettingSeekBarPreference) findPreference(CUSTOM_DIMEN); - mCustomDimen.setValue(customdimen); - mCustomDimen.setOnPreferenceChangeListener(this); - - int customdiv = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_DIV, 16, UserHandle.USER_CURRENT); - mCustomDiv = (SystemSettingSeekBarPreference) findPreference(CUSTOM_DIV); - mCustomDiv.setValue(customdiv); - mCustomDiv.setOnPreferenceChangeListener(this); - - int filled = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_FILLED_BLOCK_SIZE, 4, UserHandle.USER_CURRENT); - mFilled = (SystemSettingSeekBarPreference) findPreference(PULSE_BLOCK); - mFilled.setValue(filled); - mFilled.setOnPreferenceChangeListener(this); - - int empty = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_EMPTY_BLOCK_SIZE, 1, UserHandle.USER_CURRENT); - mEmpty = (SystemSettingSeekBarPreference) findPreference(EMPTY_BLOCK); - mEmpty.setValue(empty); - mEmpty.setOnPreferenceChangeListener(this); - - int fudge = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_FUDGE_FACTOR, 4, UserHandle.USER_CURRENT); - mFudge = (SystemSettingSeekBarPreference) findPreference(FUDGE_FACOR); - mFudge.setValue(fudge); - mFudge.setOnPreferenceChangeListener(this); - - int solidfudge = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_SOLID_FUDGE_FACTOR, 5, - UserHandle.USER_CURRENT); - mSolidFudge = (SystemSettingSeekBarPreference) findPreference(SOLID_FUDGE); - mSolidFudge.setValue(solidfudge); - mSolidFudge.setOnPreferenceChangeListener(this); - - int speed = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_LAVALAMP_SOLID_SPEED, 10000, UserHandle.USER_CURRENT); - mSolidSpeed = - (SystemSettingSeekBarPreference) findPreference(SOLID_LAVAMP_SPEED); - mSolidSpeed.setValue(speed); - mSolidSpeed.setOnPreferenceChangeListener(this); - - int fspeed = Settings.System.getIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_LAVALAMP_SPEED, 10000, UserHandle.USER_CURRENT); - mFadingSpeed = - (SystemSettingSeekBarPreference) findPreference(FADING_LAVAMP_SPEED); - mFadingSpeed.setValue(fspeed); - mFadingSpeed.setOnPreferenceChangeListener(this); - - int count = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_SOLID_UNITS_COUNT, 64, UserHandle.USER_CURRENT); - mSolidCount = - (SystemSettingSeekBarPreference) findPreference(PULSE_SOLID_UNITS_COUNT); - mSolidCount.setValue(count); - mSolidCount.setOnPreferenceChangeListener(this); - - int opacity = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_SOLID_UNITS_OPACITY, 200, UserHandle.USER_CURRENT); - mSolidOpacity = - (SystemSettingSeekBarPreference) findPreference(PULSE_SOLID_UNITS_OPACITY); - mSolidOpacity.setValue(opacity); - mSolidOpacity.setOnPreferenceChangeListener(this); - - /*int buttonsOpacity = Settings.System.getIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_BUTTONS_OPACITY, 200, UserHandle.USER_CURRENT); - mNavButtonsOpacity = - (SystemSettingSeekBarPreference) findPreference(PULSE_CUSTOM_BUTTONS_OPACITY); - mNavButtonsOpacity.setValue(buttonsOpacity); - mNavButtonsOpacity.setOnPreferenceChangeListener(this);*/ - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference.equals(mRenderMode)) { - int mode = Integer.valueOf((String) newValue); - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_RENDER_STYLE_URI, mode, UserHandle.USER_CURRENT); - PreferenceCategory fadingBarsCat = (PreferenceCategory)findPreference("pulse_fading_bars_category"); - fadingBarsCat.setEnabled(mode == RENDER_STYLE_FADING_BARS); - PreferenceCategory solidBarsCat = (PreferenceCategory)findPreference("pulse_2"); - solidBarsCat.setEnabled(mode == RENDER_STYLE_SOLID_LINES); - return true; - } else if (preference.equals(mShowPulse)) { - boolean enabled = ((Boolean) newValue).booleanValue(); - Settings.System.putIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT); - return true; - } else if (preference.equals(mAutoColor)) { - boolean enabled = ((Boolean) newValue).booleanValue(); - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_AUTO_COLOR, enabled ? 1 : 0, UserHandle.USER_CURRENT); - return true; - } else if (preference.equals(mPulseColor)) { - int color = ((Integer) newValue).intValue(); - Settings.System.putIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_COLOR, color, UserHandle.USER_CURRENT); - return true; - } else if (preference.equals(mLavaLampEnabled)) { - boolean enabled = ((Boolean) newValue).booleanValue(); - Settings.System.putIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_LAVALAMP_ENABLED, enabled ? 1 : 0, - UserHandle.USER_CURRENT); - return true; - } else if (preference == mCustomDimen) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_DIMEN, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mCustomDiv) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_DIV, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mFilled) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_FILLED_BLOCK_SIZE, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mEmpty) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_EMPTY_BLOCK_SIZE, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mFudge) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_FUDGE_FACTOR, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mSolidFudge) { - int val = (Integer) newValue; - Settings.System.putIntForUser( - getContentResolver(), - Settings.System.PULSE_SOLID_FUDGE_FACTOR, val, - UserHandle.USER_CURRENT); - return true; - } else if (preference == mSolidSpeed) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_LAVALAMP_SOLID_SPEED, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mFadingSpeed) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.FLING_PULSE_LAVALAMP_SPEED, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mSolidCount) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_SOLID_UNITS_COUNT, val, UserHandle.USER_CURRENT); - return true; - } else if (preference == mSolidOpacity) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_SOLID_UNITS_OPACITY, val, UserHandle.USER_CURRENT); - return true; - }/* else if (preference == mNavButtonsOpacity) { - int val = (Integer) newValue; - Settings.System.putIntForUser(getContentResolver(), - Settings.System.PULSE_CUSTOM_BUTTONS_OPACITY, val, UserHandle.USER_CURRENT); - return true; - }*/ - return false; - } - - @Override - public int getMetricsCategory() { - return MetricsProto.MetricsEvent.LIQUID; - } -} diff --git a/src/com/liquid/liquidlounge/fragments/StatusBarSettings.java b/src/com/liquid/liquidlounge/fragments/StatusBarSettings.java index 2a2478c3..252b8e9b 100644 --- a/src/com/liquid/liquidlounge/fragments/StatusBarSettings.java +++ b/src/com/liquid/liquidlounge/fragments/StatusBarSettings.java @@ -17,6 +17,9 @@ package com.liquid.liquidlounge.fragments; import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; @@ -41,6 +44,9 @@ public class StatusBarSettings extends SettingsPreferenceFragment private static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent"; private static final String STATUS_BAR_BATTERY_STYLE = "status_bar_battery_style"; private static final String KEY_HIDE_NOTCH = "statusbar_hide_notch"; + private static final String SMS_BREATH = "sms_breath"; + private static final String MISSED_CALL_BREATH = "missed_call_breath"; + private static final String VOICEMAIL_BREATH = "voicemail_breath"; private static final int STATUS_BAR_BATTERY_STYLE_TEXT = 3; private static final int STATUS_BAR_BATTERY_STYLE_HIDDEN = 4; @@ -50,6 +56,9 @@ public class StatusBarSettings extends SettingsPreferenceFragment private ListPreference mNetTrafficType; private ListPreference mStatusBarBatteryShowPercent; private ListPreference mStatusBarBattery; + private SwitchPreference mSmsBreath; + private SwitchPreference mMissedCallBreath; + private SwitchPreference mVoicemailBreath; @Override public void onCreate(Bundle icicle) { @@ -58,7 +67,30 @@ public void onCreate(Bundle icicle) { addPreferencesFromResource(R.xml.statusbar_settings); PreferenceScreen prefSet = getPreferenceScreen(); - final ContentResolver resolver = getActivity().getContentResolver(); + ContentResolver resolver = getActivity().getContentResolver(); + + // Breathing notifications + mSmsBreath = (SwitchPreference) findPreference(SMS_BREATH); + mMissedCallBreath = (SwitchPreference) findPreference(MISSED_CALL_BREATH); + mVoicemailBreath = (SwitchPreference) findPreference(VOICEMAIL_BREATH); + ConnectivityManager cm = (ConnectivityManager) + getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + + if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + mSmsBreath.setChecked(Settings.Global.getInt(resolver, + Settings.Global.KEY_SMS_BREATH, 0) == 1); + mSmsBreath.setOnPreferenceChangeListener(this); + mMissedCallBreath.setChecked(Settings.Global.getInt(resolver, + Settings.Global.KEY_MISSED_CALL_BREATH, 0) == 1); + mMissedCallBreath.setOnPreferenceChangeListener(this); + mVoicemailBreath.setChecked(Settings.System.getInt(resolver, + Settings.System.KEY_VOICEMAIL_BREATH, 0) == 1); + mVoicemailBreath.setOnPreferenceChangeListener(this); + } else { + prefSet.removePreference(mSmsBreath); + prefSet.removePreference(mMissedCallBreath); + prefSet.removePreference(mVoicemailBreath); + } boolean isNetMonitorEnabled = Settings.System.getIntForUser(resolver, Settings.System.NETWORK_TRAFFIC_STATE, 0, UserHandle.USER_CURRENT) == 1; @@ -139,6 +171,18 @@ public boolean onPreferenceChange(Preference preference, Object objValue) { int index = mNetTrafficType.findIndexOfValue((String) objValue); mNetTrafficType.setSummary(mNetTrafficType.getEntries()[index]); return true; + } else if (preference == mSmsBreath) { + boolean value = (Boolean) objValue; + Settings.Global.putInt(getContentResolver(), SMS_BREATH, value ? 1 : 0); + return true; + } else if (preference == mMissedCallBreath) { + boolean value = (Boolean) objValue; + Settings.Global.putInt(getContentResolver(), MISSED_CALL_BREATH, value ? 1 : 0); + return true; + } else if (preference == mVoicemailBreath) { + boolean value = (Boolean) objValue; + Settings.System.putInt(getContentResolver(), VOICEMAIL_BREATH, value ? 1 : 0); + return true; } return false; } diff --git a/src/com/liquid/liquidlounge/fragments/WeatherSettings.java b/src/com/liquid/liquidlounge/fragments/WeatherSettings.java deleted file mode 100644 index 8e9178fb..00000000 --- a/src/com/liquid/liquidlounge/fragments/WeatherSettings.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2018 The Liquid Remix 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.liquid.liquidlounge.fragments; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.UserHandle; -import android.support.v7.preference.ListPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceCategory; -import android.support.v7.preference.PreferenceScreen; -import android.provider.Settings; -import android.util.Log; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.liquid.liquidlounge.preferences.Utils; - -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; - -import java.util.List; -import java.util.ArrayList; - -public class WeatherSettings extends SettingsPreferenceFragment - implements Preference.OnPreferenceChangeListener { - - private static final String TAG = "WeatherSettings"; - - private static final String CATEGORY_WEATHER = "weather_category"; - private static final String WEATHER_ICON_PACK = "weather_icon_pack"; - private static final String DEFAULT_WEATHER_ICON_PACKAGE = "org.omnirom.omnijaws"; - private static final String DEFAULT_WEATHER_ICON_PREFIX = "google"; - private static final String WEATHER_SERVICE_PACKAGE = "org.omnirom.omnijaws"; - private static final String CHRONUS_ICON_PACK_INTENT = "com.dvtonder.chronus.ICON_PACK"; - private static final String PREF_STATUS_BAR_WEATHER = "status_bar_show_weather_temp"; - - private PreferenceCategory mWeatherCategory; - private ListPreference mWeatherIconPack; - private ListPreference mStatusBarWeather; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - addPreferencesFromResource(R.xml.weather_settings); - - final PreferenceScreen prefScreen = getPreferenceScreen(); - ContentResolver resolver = getActivity().getContentResolver(); - - mWeatherCategory = (PreferenceCategory) prefScreen.findPreference(CATEGORY_WEATHER); - if (mWeatherCategory != null && !isOmniJawsServiceInstalled()) { - prefScreen.removePreference(mWeatherCategory); - } else { - String settingHeaderPackage = Settings.System.getString(getContentResolver(), - Settings.System.OMNIJAWS_WEATHER_ICON_PACK); - if (settingHeaderPackage == null) { - settingHeaderPackage = DEFAULT_WEATHER_ICON_PACKAGE + "." + DEFAULT_WEATHER_ICON_PREFIX; - } - mWeatherIconPack = (ListPreference) findPreference(WEATHER_ICON_PACK); - - List entries = new ArrayList(); - List values = new ArrayList(); - getAvailableWeatherIconPacks(entries, values); - mWeatherIconPack.setEntries(entries.toArray(new String[entries.size()])); - mWeatherIconPack.setEntryValues(values.toArray(new String[values.size()])); - - int valueIndex = mWeatherIconPack.findIndexOfValue(settingHeaderPackage); - if (valueIndex == -1) { - // no longer found - settingHeaderPackage = DEFAULT_WEATHER_ICON_PACKAGE + "." + DEFAULT_WEATHER_ICON_PREFIX; - Settings.System.putString(getContentResolver(), - Settings.System.OMNIJAWS_WEATHER_ICON_PACK, settingHeaderPackage); - valueIndex = mWeatherIconPack.findIndexOfValue(settingHeaderPackage); - } - mWeatherIconPack.setValueIndex(valueIndex >= 0 ? valueIndex : 0); - mWeatherIconPack.setSummary(mWeatherIconPack.getEntry()); - mWeatherIconPack.setOnPreferenceChangeListener(this); - } - - // Status bar weather - mStatusBarWeather = (ListPreference) findPreference(PREF_STATUS_BAR_WEATHER); - int temperatureShow = Settings.System.getIntForUser(resolver, - Settings.System.STATUS_BAR_SHOW_WEATHER_TEMP, 0, - UserHandle.USER_CURRENT); - mStatusBarWeather.setValue(String.valueOf(temperatureShow)); - if (temperatureShow == 0) { - mStatusBarWeather.setSummary(R.string.statusbar_weather_summary); - } else { - mStatusBarWeather.setSummary(mStatusBarWeather.getEntry()); - } - mStatusBarWeather.setOnPreferenceChangeListener(this); - } - - public boolean onPreferenceChange(Preference preference, Object objValue) { - if (preference == mWeatherIconPack) { - String value = (String) objValue; - Settings.System.putString(getContentResolver(), - Settings.System.OMNIJAWS_WEATHER_ICON_PACK, value); - int valueIndex = mWeatherIconPack.findIndexOfValue(value); - mWeatherIconPack.setSummary(mWeatherIconPack.getEntries()[valueIndex]); - return true; - } else if (preference == mStatusBarWeather) { - int temperatureShow = Integer.valueOf((String) objValue); - int index = mStatusBarWeather.findIndexOfValue((String) objValue); - Settings.System.putIntForUser(getActivity().getContentResolver(), - Settings.System.STATUS_BAR_SHOW_WEATHER_TEMP, - temperatureShow, UserHandle.USER_CURRENT); - if (temperatureShow == 0) { - mStatusBarWeather.setSummary(R.string.statusbar_weather_summary); - } else { - mStatusBarWeather.setSummary( - mStatusBarWeather.getEntries()[index]); - } - return true; - } - return false; - } - - @Override - public void onResume() { - super.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - } - - private boolean isOmniJawsServiceInstalled() { - return Utils.isAvailableApp(WEATHER_SERVICE_PACKAGE, getActivity()); - } - - private void getAvailableWeatherIconPacks(List entries, List values) { - Intent i = new Intent(); - PackageManager packageManager = getPackageManager(); - i.setAction("org.omnirom.WeatherIconPack"); - for (ResolveInfo r : packageManager.queryIntentActivities(i, 0)) { - String packageName = r.activityInfo.packageName; - if (packageName.equals(DEFAULT_WEATHER_ICON_PACKAGE)) { - values.add(0, r.activityInfo.name); - } else { - values.add(r.activityInfo.name); - } - String label = r.activityInfo.loadLabel(getPackageManager()).toString(); - if (label == null) { - label = r.activityInfo.packageName; - } - if (packageName.equals(DEFAULT_WEATHER_ICON_PACKAGE)) { - entries.add(0, label); - } else { - entries.add(label); - } - } - i = new Intent(Intent.ACTION_MAIN); - i.addCategory(CHRONUS_ICON_PACK_INTENT); - for (ResolveInfo r : packageManager.queryIntentActivities(i, 0)) { - String packageName = r.activityInfo.packageName; - values.add(packageName + ".weather"); - String label = r.activityInfo.loadLabel(getPackageManager()).toString(); - if (label == null) { - label = r.activityInfo.packageName; - } - entries.add(label); - } - } - - private boolean isOmniJawsEnabled() { - final Uri SETTINGS_URI - = Uri.parse("content://org.omnirom.omnijaws.provider/settings"); - - final String[] SETTINGS_PROJECTION = new String[] { - "enabled" - }; - - final Cursor c = getContentResolver().query(SETTINGS_URI, SETTINGS_PROJECTION, - null, null, null); - if (c != null) { - int count = c.getCount(); - if (count == 1) { - c.moveToPosition(0); - boolean enabled = c.getInt(0) == 1; - return enabled; - } - } - return true; - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.LIQUID; - } -} diff --git a/src/com/liquid/liquidlounge/preferences/Utils.java b/src/com/liquid/liquidlounge/preferences/Utils.java index 1fb9b2cc..1a89e85a 100644 --- a/src/com/liquid/liquidlounge/preferences/Utils.java +++ b/src/com/liquid/liquidlounge/preferences/Utils.java @@ -205,27 +205,4 @@ public static boolean deviceSupportsFlashLight(Context context) { } return false; } - - public static boolean isAppInstalled(Context context, String appUri) { - try { - PackageManager pm = context.getPackageManager(); - pm.getPackageInfo(appUri, PackageManager.GET_ACTIVITIES); - return true; - } catch (Exception e) { - return false; - } - } - - public static boolean isAvailableApp(String packageName, Context context) { - Context mContext = context; - final PackageManager pm = mContext.getPackageManager(); - try { - pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES); - int enabled = pm.getApplicationEnabledSetting(packageName); - return enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED && - enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; - } catch (NameNotFoundException e) { - return false; - } - } } diff --git a/src/com/liquid/liquidlounge/preferences/dslv/ActionListViewSettings.java b/src/com/liquid/liquidlounge/preferences/dslv/ActionListViewSettings.java new file mode 100644 index 00000000..54670f9e --- /dev/null +++ b/src/com/liquid/liquidlounge/preferences/dslv/ActionListViewSettings.java @@ -0,0 +1,966 @@ +/* + * Copyright (C) 2014 Slimroms + * + * 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.liquid.liquidlounge.preferences.dslv; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.ListFragment; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.ListView; +import android.widget.Toast; + +import com.android.internal.util.slim.ActionConfig; +import com.android.internal.util.slim.ActionConstants; +import com.android.internal.util.slim.ActionHelper; +import com.android.internal.util.slim.ImageHelper; +import com.android.internal.util.slim.DeviceUtils; +import com.android.internal.util.slim.DeviceUtils.FilteredDeviceFeaturesArray; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.R; +import com.liquid.liquidlounge.preferences.dslv.DragSortListView; +import com.liquid.liquidlounge.preferences.dslv.DragSortController; +import com.liquid.liquidlounge.preferences.dslv.ShortcutPickerHelper; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ActionListViewSettings extends ListFragment implements + ShortcutPickerHelper.OnPickListener { + + private static final int DLG_SHOW_ACTION_DIALOG = 0; + private static final int DLG_SHOW_ICON_PICKER = 1; + private static final int DLG_DELETION_NOT_ALLOWED = 2; + private static final int DLG_SHOW_HELP_SCREEN = 3; + private static final int DLG_RESET_TO_DEFAULT = 4; + + private static final int MENU_HELP = Menu.FIRST; + private static final int MENU_ADD = MENU_HELP + 1; + private static final int MENU_RESET = MENU_ADD + 1; + + private static final int NAV_BAR = 0; + private static final int PIE = 1; + private static final int PIE_SECOND = 2; + private static final int NAV_RING = 3; + private static final int LOCKSCREEN_SHORTCUT = 4; + private static final int POWER_MENU_SHORTCUT = 5; + private static final int SHAKE_EVENTS_DISABLED = 6; + + private static final int DEFAULT_MAX_ACTION_NUMBER = 5; + private static final int DEFAULT_NUMBER_OF_ACTIONS = 3; + + public static final int REQUEST_PICK_CUSTOM_ICON = 1000; + + private int mActionMode; + private int mMaxAllowedActions; + private int mDefaultNumberOfActions; + private boolean mUseAppPickerOnly; + private boolean mUseFullAppsOnly; + private boolean mDisableLongpress; + private boolean mDisableIconPicker; + private boolean mDisableDeleteLastEntry; + + private TextView mDisableMessage; + + private ActionConfigsAdapter mActionConfigsAdapter; + + private ArrayList mActionConfigs; + private ActionConfig mActionConfig; + + private boolean mAdditionalFragmentAttached; + private String mAdditionalFragment; + private View mDivider; + private View mFab; + + private int mPendingIndex = -1; + private boolean mPendingLongpress; + private boolean mPendingNewAction; + + private String[] mActionDialogValues; + private String[] mActionDialogEntries; + private String mActionValuesKey; + private String mActionEntriesKey; + + private Activity mActivity; + private ShortcutPickerHelper mPicker; + + private File mImageTmp; + + private DragSortListView.DropListener onDrop = + new DragSortListView.DropListener() { + @Override + public void drop(int from, int to) { + ActionConfig item = mActionConfigsAdapter.getItem(from); + + mActionConfigsAdapter.remove(item); + mActionConfigsAdapter.insert(item, to); + + setConfig(mActionConfigs, false); + } + }; + + private DragSortListView.RemoveListener onRemove = + new DragSortListView.RemoveListener() { + @Override + public void remove(int which) { + ActionConfig item = mActionConfigsAdapter.getItem(which); + mActionConfigsAdapter.remove(item); + if (mDisableDeleteLastEntry && mActionConfigs.size() == 0) { + mActionConfigsAdapter.add(item); + showDialogInner(DLG_DELETION_NOT_ALLOWED, 0, false, false); + } else { + deleteIconFileIfPresent(item, true); + setConfig(mActionConfigs, false); + if (mActionConfigs.size() == 0) { + showDisableMessage(true); + } + } + } + }; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + return inflater.inflate(R.layout.action_list_view_main, container, false); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mActivity = activity; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Resources res = getResources(); + + mActionMode = getArguments().getInt("actionMode", NAV_BAR); + mMaxAllowedActions = getArguments().getInt("maxAllowedActions", DEFAULT_MAX_ACTION_NUMBER); + mDefaultNumberOfActions = getArguments().getInt("defaultNumberOfActions", DEFAULT_NUMBER_OF_ACTIONS); + mAdditionalFragment = getArguments().getString("fragment", null); + mActionValuesKey = getArguments().getString("actionValues", "shortcut_action_values"); + mActionEntriesKey = getArguments().getString("actionEntries", "shortcut_action_entries"); + mDisableLongpress = getArguments().getBoolean("disableLongpress", false); + mUseAppPickerOnly = getArguments().getBoolean("useAppPickerOnly", false); + mUseFullAppsOnly = getArguments().getBoolean("useOnlyFullAppPicker", false); + mDisableIconPicker = getArguments().getBoolean("disableIconPicker", false); + mDisableDeleteLastEntry = getArguments().getBoolean("disableDeleteLastEntry", false); + + mDisableMessage = (TextView) view.findViewById(R.id.disable_message); + + FilteredDeviceFeaturesArray finalActionDialogArray = new FilteredDeviceFeaturesArray(); + finalActionDialogArray = DeviceUtils.filterUnsupportedDeviceFeatures(mActivity, + res.getStringArray(res.getIdentifier( + mActionValuesKey, "array", "com.android.settings")), + res.getStringArray(res.getIdentifier( + mActionEntriesKey, "array", "com.android.settings"))); + mActionDialogValues = finalActionDialogArray.values; + mActionDialogEntries = finalActionDialogArray.entries; + + mPicker = new ShortcutPickerHelper(mActivity, this); + + mImageTmp = new File(mActivity.getCacheDir() + + File.separator + "shortcut.tmp"); + + DragSortListView listView = (DragSortListView) getListView(); + + listView.setDropListener(onDrop); + listView.setRemoveListener(onRemove); + + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, + long arg3) { + if (mUseFullAppsOnly) { + if (mPicker != null) { + mPendingIndex = arg2; + mPendingLongpress = false; + mPendingNewAction = false; + mPicker.pickShortcut(getId(), true); + } + } else if (!mUseAppPickerOnly) { + showDialogInner(DLG_SHOW_ACTION_DIALOG, arg2, false, false); + } else { + if (mPicker != null) { + mPendingIndex = arg2; + mPendingLongpress = false; + mPendingNewAction = false; + mPicker.pickShortcut(getId(), false, false, true); + } + } + } + }); + + if (!mDisableLongpress) { + listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, + long arg3) { + if (mUseFullAppsOnly) { + if (mPicker != null) { + mPendingIndex = arg2; + mPendingLongpress = true; + mPendingNewAction = false; + mPicker.pickShortcut(getId(), true); + } + } else if (!mUseAppPickerOnly) { + showDialogInner(DLG_SHOW_ACTION_DIALOG, arg2, true, false); + } else { + if (mPicker != null) { + mPendingIndex = arg2; + mPendingLongpress = true; + mPendingNewAction = false; + mPicker.pickShortcut(getId()); + } + } + return true; + } + }); + } + + mActionConfigs = getConfig(); + + if (mActionConfigs != null) { + mActionConfigsAdapter = new ActionConfigsAdapter(mActivity, mActionConfigs); + setListAdapter(mActionConfigsAdapter); + showDisableMessage(mActionConfigs.size() == 0); + } + + mDivider = (View) view.findViewById(R.id.divider); + loadAdditionalFragment(); + + mFab = view.findViewById(R.id.floating_action_button); + mFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mUseFullAppsOnly) { + if (mPicker != null) { + mPendingIndex = 0; + mPendingLongpress = false; + mPendingNewAction = true; + mPicker.pickShortcut(getId(), true); + } + } else if (!mUseAppPickerOnly) { + showDialogInner(DLG_SHOW_ACTION_DIALOG, 0, false, true); + } else { + if (mPicker != null) { + mPendingIndex = 0; + mPendingLongpress = false; + mPendingNewAction = true; + mPicker.pickShortcut(getId(), false, true, false); + } + } + } + }); + updateFabVisibility(mActionConfigs.size()); + + // get shared preference + SharedPreferences preferences = + mActivity.getSharedPreferences("dslv_settings", Activity.MODE_PRIVATE); + if (!preferences.getBoolean("first_help_shown_mode_" + mActionMode, false)) { + preferences.edit() + .putBoolean("first_help_shown_mode_" + mActionMode, true).commit(); + showDialogInner(DLG_SHOW_HELP_SCREEN, 0, false, false); + } + + setHasOptionsMenu(true); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mAdditionalFragmentAttached) { + FragmentManager fragmentManager = getFragmentManager(); + Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container); + if (fragment != null && !fragmentManager.isDestroyed()) { + fragmentManager.beginTransaction().remove(fragment).commit(); + } + } + } + + private void loadAdditionalFragment() { + if (mAdditionalFragment != null && !mAdditionalFragment.isEmpty()) { + try { + Class classAdditionalFragment = Class.forName(mAdditionalFragment); + Fragment fragment = (Fragment) classAdditionalFragment.newInstance(); + getFragmentManager().beginTransaction() + .replace(R.id.fragment_container, fragment).commit(); + if (mDivider != null) { + mDivider.setVisibility(View.VISIBLE); + } + mAdditionalFragmentAttached = true; + } catch (Exception e) { + mAdditionalFragmentAttached = false; + e.printStackTrace(); + } + } + } + + @Override + public void shortcutPicked(String action, + String description, Bitmap bmp, boolean isApplication) { + if (mPendingIndex == -1) { + return; + } + if (bmp != null && !mPendingLongpress) { + // Icon is present, save it for future use and add the file path to the action. + String fileName = mActivity.getFilesDir() + + File.separator + "shortcut_" + System.currentTimeMillis() + ".png"; + try { + FileOutputStream out = new FileOutputStream(fileName); + bmp.compress(Bitmap.CompressFormat.PNG, 100, out); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + action = action + "?hasExtraIcon=" + fileName; + File image = new File(fileName); + image.setReadable(true, false); + } + } + if (mPendingNewAction) { + addNewAction(action, description); + } else { + updateAction(action, description, null, mPendingIndex, mPendingLongpress); + } + mPendingLongpress = false; + mPendingNewAction = false; + mPendingIndex = -1; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == ShortcutPickerHelper.REQUEST_PICK_SHORTCUT + || requestCode == ShortcutPickerHelper.REQUEST_PICK_APPLICATION + || requestCode == ShortcutPickerHelper.REQUEST_CREATE_SHORTCUT) { + mPicker.onActivityResult(requestCode, resultCode, data); + + } else if (requestCode == REQUEST_PICK_CUSTOM_ICON && mPendingIndex != -1) { + if (mImageTmp.length() == 0 || !mImageTmp.exists()) { + mPendingIndex = -1; + Toast.makeText(mActivity, + getResources().getString(R.string.shortcut_image_not_valid), + Toast.LENGTH_LONG).show(); + return; + } + File image = new File(mActivity.getFilesDir() + File.separator + + "shortcut_" + System.currentTimeMillis() + ".png"); + String path = image.getAbsolutePath(); + mImageTmp.renameTo(image); + image.setReadable(true, false); + updateAction(null, null, path, mPendingIndex, false); + mPendingIndex = -1; + } + } else { + if (mImageTmp.exists()) { + mImageTmp.delete(); + } + mPendingLongpress = false; + mPendingNewAction = false; + mPendingIndex = -1; + } + super.onActivityResult(requestCode, resultCode, data); + } + + private void updateAction(String action, String description, String icon, + int which, boolean longpress) { + + if (!longpress && checkForDuplicateMainNavActions(action)) { + return; + } + + ActionConfig actionConfig = mActionConfigsAdapter.getItem(which); + mActionConfigsAdapter.remove(actionConfig); + + if (!longpress) { + deleteIconFileIfPresent(actionConfig, false); + } + + if (icon != null) { + actionConfig.setIcon(icon); + } else { + if (longpress) { + actionConfig.setLongpressAction(action); + actionConfig.setLongpressActionDescription(description); + } else { + deleteIconFileIfPresent(actionConfig, true); + actionConfig.setClickAction(action); + actionConfig.setClickActionDescription(description); + actionConfig.setIcon(ActionConstants.ICON_EMPTY); + } + } + + mActionConfigsAdapter.insert(actionConfig, which); + showDisableMessage(false); + setConfig(mActionConfigs, false); + } + + private boolean checkForDuplicateMainNavActions(String action) { + ActionConfig actionConfig; + for (int i = 0; i < mActionConfigs.size(); i++) { + actionConfig = mActionConfigsAdapter.getItem(i); + if (actionConfig.getClickAction().equals(action)) { + Toast.makeText(mActivity, + getResources().getString(R.string.shortcut_duplicate_entry), + Toast.LENGTH_LONG).show(); + return true; + } + } + return false; + } + + private void deleteIconFileIfPresent(ActionConfig action, boolean deleteShortCutIcon) { + File oldImage = new File(action.getIcon()); + if (oldImage.exists()) { + oldImage.delete(); + } + oldImage = new File(action.getClickAction().replaceAll(".*?hasExtraIcon=", "")); + if (oldImage.exists() && deleteShortCutIcon) { + oldImage.delete(); + } + } + + private void showDisableMessage(boolean show) { + if (mDisableMessage == null || mDisableDeleteLastEntry) { + return; + } + if (show) { + mDisableMessage.setVisibility(View.VISIBLE); + } else { + mDisableMessage.setVisibility(View.GONE); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + showDialogInner(DLG_RESET_TO_DEFAULT, 0, false, true); + break; + case MENU_HELP: + showDialogInner(DLG_SHOW_HELP_SCREEN, 0, false, true); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_HELP, 0, R.string.help) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, MENU_RESET, 0, R.string.shortcut_action_reset) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + + private void addNewAction(String action, String description) { + if (checkForDuplicateMainNavActions(action)) { + return; + } + ActionConfig actionConfig = new ActionConfig( + action, description, + ActionConstants.ACTION_NULL, getResources().getString(R.string.shortcut_action_none), + ActionConstants.ICON_EMPTY); + + mActionConfigsAdapter.add(actionConfig); + showDisableMessage(false); + setConfig(mActionConfigs, false); + } + + private ArrayList getConfig() { + switch (mActionMode) { + case LOCKSCREEN_SHORTCUT: + return ActionHelper.getLockscreenShortcutConfig(mActivity); +/* Disabled for now till all features are back. Enable it step per step!!!!!! + case NAV_BAR: + return ActionHelper.getNavBarConfigWithDescription( + mActivity, mActionValuesKey, mActionEntriesKey); + case NAV_RING: + return ActionHelper.getNavRingConfigWithDescription( + mActivity, mActionValuesKey, mActionEntriesKey); + case PIE: + return ActionHelper.getPieConfigWithDescription( + mActivity, mActionValuesKey, mActionEntriesKey); + case PIE_SECOND: + return ActionHelper.getPieSecondLayerConfigWithDescription( + mActivity, mActionValuesKey, mActionEntriesKey); + case POWER_MENU_SHORTCUT: + return PolicyHelper.getPowerMenuConfigWithDescription( + mActivity, mActionValuesKey, mActionEntriesKey); + case SHAKE_EVENTS_DISABLED: + return ActionHelper.getDisabledShakeApps(mActivity); +*/ + } + return null; + } + + private void setConfig(ArrayList actionConfigs, boolean reset) { + switch (mActionMode) { + case LOCKSCREEN_SHORTCUT: + ActionHelper.setLockscreenShortcutConfig(mActivity, actionConfigs, reset); + updateFabVisibility(reset ? mDefaultNumberOfActions : actionConfigs.size()); + break; +/* Disabled for now till all features are back. Enable it step per step!!!!!! + case NAV_BAR: + ActionHelper.setNavBarConfig(mActivity, actionConfigs, reset); + updateFabVisibility(reset ? mDefaultNumberOfActions : actionConfigs.size()); + break; + case NAV_RING: + ActionHelper.setNavRingConfig(mActivity, actionConfigs, reset); + break; + case PIE: + ActionHelper.setPieConfig(mActivity, actionConfigs, reset); + updateFabVisibility(reset ? mDefaultNumberOfActions : actionConfigs.size()); + break; + case PIE_SECOND: + ActionHelper.setPieSecondLayerConfig(mActivity, actionConfigs, reset); + updateFabVisibility(reset ? mDefaultNumberOfActions : actionConfigs.size()); + break; + case POWER_MENU_SHORTCUT: + PolicyHelper.setPowerMenuConfig(mActivity, actionConfigs, reset); + break; + case SHAKE_EVENTS_DISABLED: + ActionHelper.setDisabledShakeApps(mActivity, actionConfigs, reset); + break; +*/ + } + } + + private void updateFabVisibility(int numberOfActions) { + if (numberOfActions == mMaxAllowedActions) { + mFab.setVisibility(View.GONE); + } else { + mFab.setVisibility(View.VISIBLE); + } + } + + private class ViewHolder { + public TextView longpressActionDescriptionView; + public ImageView iconView; + } + + private class ActionConfigsAdapter extends ArrayAdapter { + + public ActionConfigsAdapter(Context context, List clickActionDescriptions) { + super(context, R.layout.action_list_view_item, + R.id.click_action_description, clickActionDescriptions); + } + + public View getView(final int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + if (v != convertView && v != null) { + ViewHolder holder = new ViewHolder(); + + TextView longpressActionDecription = + (TextView) v.findViewById(R.id.longpress_action_description); + ImageView icon = (ImageView) v.findViewById(R.id.icon); + + if (mDisableLongpress) { + longpressActionDecription.setVisibility(View.GONE); + } else { + holder.longpressActionDescriptionView = longpressActionDecription; + } + + holder.iconView = icon; + + v.setTag(holder); + } + + ViewHolder holder = (ViewHolder) v.getTag(); + + if (!mDisableLongpress) { + holder.longpressActionDescriptionView.setText( + getResources().getString(R.string.shortcut_action_longpress) + + " " + getItem(position).getLongpressActionDescription()); + } + + Drawable d = null; + String iconUri = getItem(position).getIcon(); + if (mActionMode == POWER_MENU_SHORTCUT) { +/* Disabled for now till slims power menu is back!!!!!!!!!!!!!! + d = ImageHelper.resize( + mActivity, PolicyHelper.getPowerMenuIconImage(mActivity, + getItem(position).getClickAction(), + iconUri, false), 36); */ + } else { + d = ImageHelper.resize( + mActivity, ActionHelper.getActionIconImage(mActivity, + getItem(position).getClickAction(), + iconUri), 48); + } + + holder.iconView.setImageDrawable(d); + + if (!mDisableIconPicker && holder.iconView.getDrawable() != null) { + holder.iconView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mPendingIndex = position; + showDialogInner(DLG_SHOW_ICON_PICKER, 0, false, false); + } + }); + } + + return v; + } + } + + private void showDialogInner(int id, int which, boolean longpress, boolean newAction) { + DialogFragment newFragment = + MyAlertDialogFragment.newInstance(id, which, longpress, newAction); + newFragment.setTargetFragment(this, 0); + newFragment.show(getFragmentManager(), "dialog " + id); + } + + public static class MyAlertDialogFragment extends DialogFragment { + + public static MyAlertDialogFragment newInstance(int id, + int which, boolean longpress, boolean newAction) { + MyAlertDialogFragment frag = new MyAlertDialogFragment(); + Bundle args = new Bundle(); + args.putInt("id", id); + args.putInt("which", which); + args.putBoolean("longpress", longpress); + args.putBoolean("newAction", newAction); + frag.setArguments(args); + return frag; + } + + ActionListViewSettings getOwner() { + return (ActionListViewSettings) getTargetFragment(); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + int id = getArguments().getInt("id"); + final int which = getArguments().getInt("which"); + final boolean longpress = getArguments().getBoolean("longpress"); + final boolean newAction = getArguments().getBoolean("newAction"); + switch (id) { + case DLG_RESET_TO_DEFAULT: + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.shortcut_action_reset) + .setMessage(R.string.reset_message) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // first delete custom icons in case they exist + ArrayList actionConfigs = getOwner().getConfig(); + for (int i = 0; i < actionConfigs.size(); i++) { + getOwner().deleteIconFileIfPresent(actionConfigs.get(i), true); + } + + // reset provider values and action adapter to default + getOwner().setConfig(null, true); + getOwner().mActionConfigsAdapter.clear(); + + // Add the new default objects fetched from @getConfig() + actionConfigs = getOwner().getConfig(); + final int newConfigsSize = actionConfigs.size(); + for (int i = 0; i < newConfigsSize; i++) { + getOwner().mActionConfigsAdapter.add(actionConfigs.get(i)); + } + + // dirty helper if actionConfigs list has no entries + // to proper update the content. .notifyDatSetChanged() + // does not work in this case. + if (newConfigsSize == 0) { + ActionConfig emptyAction = + new ActionConfig(null, null, null, null, null); + getOwner().mActionConfigsAdapter.add(emptyAction); + getOwner().mActionConfigsAdapter.remove(emptyAction); + } + getOwner().showDisableMessage(newConfigsSize == 0); + } + }) + .create(); + case DLG_SHOW_HELP_SCREEN: + Resources res = getResources(); + String finalHelpMessage; + String actionMode; + String icon = ""; + switch (getOwner().mActionMode) { + case LOCKSCREEN_SHORTCUT: + case POWER_MENU_SHORTCUT: + actionMode = res.getString(R.string.shortcut_action_help_shortcut); + break; + case SHAKE_EVENTS_DISABLED: + actionMode = res.getString(R.string.shortcut_action_help_app); + break; + case NAV_BAR: + case NAV_RING: + case PIE: + case PIE_SECOND: + default: + actionMode = res.getString(R.string.shortcut_action_help_button); + break; + } + if (!getOwner().mDisableIconPicker) { + icon = res.getString(R.string.shortcut_action_help_icon); + } + finalHelpMessage = res.getString( + R.string.shortcut_action_help_main, actionMode, icon); + if (!getOwner().mDisableDeleteLastEntry) { + finalHelpMessage += " " + res.getString( + getOwner().mActionMode == PIE_SECOND + ? R.string.shortcut_action_help_pie_second_layer_delete_last_entry + : R.string.shortcut_action_help_delete_last_entry, actionMode); + } + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.help_label) + .setMessage(finalHelpMessage) + .setNegativeButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }) + .create(); + case DLG_DELETION_NOT_ALLOWED: + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.shortcut_action_warning) + .setMessage(R.string.shortcut_action_warning_message) + .setNegativeButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }) + .create(); + case DLG_SHOW_ACTION_DIALOG: + int title; + if (longpress) { + title = R.string.shortcut_action_select_action_longpress; + } else if (newAction) { + title = R.string.shortcut_action_select_action_newaction; + } else { + title = R.string.shortcut_action_select_action; + } + + // for normal press action we filter out null value + // due it does not make sense to set a null action + // on normal press action + String[] values = null; + String[] entries = null; + if (!longpress) { + List finalEntriesList = new ArrayList(); + List finalValuesList = new ArrayList(); + + for (int i = 0; i < getOwner().mActionDialogValues.length; i++) { + if (!getOwner().mActionDialogValues[i] + .equals(ActionConstants.ACTION_NULL)) { + finalEntriesList.add(getOwner().mActionDialogEntries[i]); + finalValuesList.add(getOwner().mActionDialogValues[i]); + } + } + + entries = finalEntriesList.toArray(new String[finalEntriesList.size()]); + values = finalValuesList.toArray(new String[finalValuesList.size()]); + } + + final String[] finalDialogValues = + longpress ? getOwner().mActionDialogValues : values; + final String[] finalDialogEntries = + longpress ? getOwner().mActionDialogEntries : entries; + + return new AlertDialog.Builder(getActivity()) + .setTitle(title) + .setNegativeButton(R.string.cancel, null) + .setItems(finalDialogEntries, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (finalDialogValues[item].equals(ActionConstants.ACTION_APP)) { + if (getOwner().mPicker != null) { + getOwner().mPendingIndex = which; + getOwner().mPendingLongpress = longpress; + getOwner().mPendingNewAction = newAction; + getOwner().mPicker.pickShortcut(getOwner().getId()); + } + } else { + if (newAction) { + getOwner().addNewAction(finalDialogValues[item], + finalDialogEntries[item]); + } else { + getOwner().updateAction(finalDialogValues[item], + finalDialogEntries[item], + null, which, longpress); + } + } + } + }) + .create(); + case DLG_SHOW_ICON_PICKER: + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.shortcuts_icon_picker_type) + .setNegativeButton(R.string.cancel, null) + .setItems(R.array.icon_types, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + switch(which) { + case 0: // Default + getOwner().updateAction(null, null, + ActionConstants.ICON_EMPTY, + getOwner().mPendingIndex, false); + getOwner().mPendingIndex = -1; + break; + case 1: // System defaults + ListView list = new ListView(getActivity()); + list.setAdapter(new IconAdapter()); + final Dialog holoDialog = new Dialog(getActivity()); + holoDialog.setTitle( + R.string.shortcuts_icon_picker_choose_icon_title); + holoDialog.setContentView(list); + list.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, + int position, long id) { + IconAdapter adapter = (IconAdapter) parent.getAdapter(); + getOwner().updateAction(null, null, + adapter.getItemReference(position), + getOwner().mPendingIndex, false); + getOwner().mPendingIndex = -1; + holoDialog.cancel(); + } + }); + holoDialog.show(); + break; + case 2: // Custom user icon + Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); + intent.setType("image/*"); + intent.putExtra("crop", "true"); + intent.putExtra("scale", true); + intent.putExtra("outputFormat", + Bitmap.CompressFormat.PNG.toString()); + intent.putExtra("aspectX", 100); + intent.putExtra("aspectY", 100); + intent.putExtra("outputX", 100); + intent.putExtra("outputY", 100); + try { + getOwner().mImageTmp.createNewFile(); + getOwner().mImageTmp.setWritable(true, false); + intent.putExtra(MediaStore.EXTRA_OUTPUT, + Uri.fromFile(getOwner().mImageTmp)); + intent.putExtra("return-data", false); + getOwner().startActivityForResult( + intent, REQUEST_PICK_CUSTOM_ICON); + } catch (IOException e) { + e.printStackTrace(); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + break; + } + } + }) + .create(); + } + throw new IllegalArgumentException("unknown id " + id); + } + + @Override + public void onCancel(DialogInterface dialog) { + + } + + public class IconAdapter extends BaseAdapter { + + TypedArray icons; + String[] labels; + int color; + + public IconAdapter() { + labels = getResources().getStringArray(R.array.shortcut_icon_picker_labels); + icons = getResources().obtainTypedArray(R.array.shortcut_icon_picker_icons); + } + + @Override + public Object getItem(int position) { + return icons.getDrawable(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public int getCount() { + return labels.length; + } + + public String getItemReference(int position) { + String name = icons.getString(position); + int separatorIndex = name.lastIndexOf(File.separator); + int periodIndex = name.lastIndexOf('.'); + return ActionConstants.SYSTEM_ICON_IDENTIFIER + + name.substring(separatorIndex + 1, periodIndex); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View iView = convertView; + if (convertView == null) { + iView = View.inflate(getActivity(), android.R.layout.simple_list_item_1, null); + } + TextView tt = (TextView) iView.findViewById(android.R.id.text1); + tt.setText(labels[position]); + Drawable ic = ((Drawable) getItem(position)).mutate(); + tt.setCompoundDrawablePadding(15); + tt.setCompoundDrawablesWithIntrinsicBounds(ic, null, null, null); + return iView; + } + } + + } + +} diff --git a/src/com/liquid/liquidlounge/preferences/dslv/DragSortController.java b/src/com/liquid/liquidlounge/preferences/dslv/DragSortController.java new file mode 100644 index 00000000..3e3be2c2 --- /dev/null +++ b/src/com/liquid/liquidlounge/preferences/dslv/DragSortController.java @@ -0,0 +1,488 @@ +/* + * Copyright 2012 Carl Bauer + * Copyright (C) 2014 SlimRoms 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.liquid.liquidlounge.preferences.dslv; + +import android.graphics.Point; +import android.view.GestureDetector; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.AdapterView; + +/** + * Class that starts and stops item drags on a {@link DragSortListView} + * based on touch gestures. This class also inherits from + * {@link SimpleFloatViewManager}, which provides basic float View + * creation. + * + * An instance of this class is meant to be passed to the methods + * {@link DragSortListView#setTouchListener()} and + * {@link DragSortListView#setFloatViewManager()} of your + * {@link DragSortListView} instance. + */ +public class DragSortController extends SimpleFloatViewManager + implements View.OnTouchListener, GestureDetector.OnGestureListener { + + /** + * Drag init mode enum. + */ + public static final int ON_DOWN = 0; + public static final int ON_DRAG = 1; + public static final int ON_LONG_PRESS = 2; + + private int mDragInitMode = ON_DOWN; + + private boolean mSortEnabled = true; + + /** + * Remove mode enum. + */ + public static final int CLICK_REMOVE = 0; + public static final int FLING_REMOVE = 1; + + /** + * The current remove mode. + */ + private int mRemoveMode; + + private boolean mRemoveEnabled = false; + private boolean mIsRemoving = false; + + private GestureDetector mDetector; + + private GestureDetector mFlingRemoveDetector; + + private int mTouchSlop; + + public static final int MISS = -1; + + private int mHitPos = MISS; + private int mFlingHitPos = MISS; + + private int mClickRemoveHitPos = MISS; + + private int[] mTempLoc = new int[2]; + + private int mItemX; + private int mItemY; + + private int mCurrX; + private int mCurrY; + + private boolean mDragging = false; + + private float mFlingSpeed = 500f; + + private int mDragHandleId; + + private int mClickRemoveId; + + private int mFlingHandleId; + private boolean mCanDrag; + + private DragSortListView mDslv; + private int mPositionX; + + /** + * Calls {@link #DragSortController(DragSortListView, int)} with a + * 0 drag handle id, FLING_RIGHT_REMOVE remove mode, + * and ON_DOWN drag init. By default, sorting is enabled, and + * removal is disabled. + * + * @param dslv The DSLV instance + */ + public DragSortController(DragSortListView dslv) { + this(dslv, 0, ON_DOWN, FLING_REMOVE); + } + + public DragSortController(DragSortListView dslv, + int dragHandleId, int dragInitMode, int removeMode) { + this(dslv, dragHandleId, dragInitMode, removeMode, 0); + } + + public DragSortController(DragSortListView dslv, int dragHandleId, + int dragInitMode, int removeMode, int clickRemoveId) { + this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0); + } + + /** + * By default, sorting is enabled, and removal is disabled. + * + * @param dslv The DSLV instance + * @param dragHandleId The resource id of the View that represents + * the drag handle in a list item. + */ + public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, + int removeMode, int clickRemoveId, int flingHandleId) { + super(dslv); + mDslv = dslv; + mDetector = new GestureDetector(dslv.getContext(), this); + mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener); + mFlingRemoveDetector.setIsLongpressEnabled(false); + mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop(); + mDragHandleId = dragHandleId; + mClickRemoveId = clickRemoveId; + mFlingHandleId = flingHandleId; + setRemoveMode(removeMode); + setDragInitMode(dragInitMode); + } + + + public int getDragInitMode() { + return mDragInitMode; + } + + /** + * Set how a drag is initiated. Needs to be one of + * {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}. + * + * @param mode The drag init mode. + */ + public void setDragInitMode(int mode) { + mDragInitMode = mode; + } + + /** + * Enable/Disable list item sorting. Disabling is useful if only item + * removal is desired. Prevents drags in the vertical direction. + * + * @param enabled Set true to enable list + * item sorting. + */ + public void setSortEnabled(boolean enabled) { + mSortEnabled = enabled; + } + + public boolean isSortEnabled() { + return mSortEnabled; + } + + /** + * One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE}, + * {@link FLING_LEFT_REMOVE}, + * {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}. + */ + public void setRemoveMode(int mode) { + mRemoveMode = mode; + } + + public int getRemoveMode() { + return mRemoveMode; + } + + /** + * Enable/Disable item removal without affecting remove mode. + */ + public void setRemoveEnabled(boolean enabled) { + mRemoveEnabled = enabled; + } + + public boolean isRemoveEnabled() { + return mRemoveEnabled; + } + + /** + * Set the resource id for the View that represents the drag + * handle in a list item. + * + * @param id An android resource id. + */ + public void setDragHandleId(int id) { + mDragHandleId = id; + } + + /** + * Set the resource id for the View that represents the fling + * handle in a list item. + * + * @param id An android resource id. + */ + public void setFlingHandleId(int id) { + mFlingHandleId = id; + } + + /** + * Set the resource id for the View that represents click + * removal button. + * + * @param id An android resource id. + */ + public void setClickRemoveId(int id) { + mClickRemoveId = id; + } + + /** + * Sets flags to restrict certain motions of the floating View + * based on DragSortController settings (such as remove mode). + * Starts the drag on the DragSortListView. + * + * @param position The list item position (includes headers). + * @param deltaX Touch x-coord minus left edge of floating View. + * @param deltaY Touch y-coord minus top edge of floating View. + * + * @return True if drag started, false otherwise. + */ + public boolean startDrag(int position, int deltaX, int deltaY) { + + int dragFlags = 0; + if (mSortEnabled && !mIsRemoving) { + dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y; + } + if (mRemoveEnabled && mIsRemoving) { + dragFlags |= DragSortListView.DRAG_POS_X; + dragFlags |= DragSortListView.DRAG_NEG_X; + } + + mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX, + deltaY); + return mDragging; + } + + @Override + public boolean onTouch(View v, MotionEvent ev) { + if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) { + return false; + } + + mDetector.onTouchEvent(ev); + if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) { + mFlingRemoveDetector.onTouchEvent(ev); + } + + int action = ev.getAction() & MotionEvent.ACTION_MASK; + switch (action) { + case MotionEvent.ACTION_DOWN: + mCurrX = (int) ev.getX(); + mCurrY = (int) ev.getY(); + break; + case MotionEvent.ACTION_UP: + if (mRemoveEnabled && mIsRemoving) { + int x = mPositionX >= 0 ? mPositionX : -mPositionX; + int removePoint = mDslv.getWidth() / 2; + if (x > removePoint) { + mDslv.stopDragWithVelocity(true, 0); + } + } + case MotionEvent.ACTION_CANCEL: + mIsRemoving = false; + mDragging = false; + break; + } + + return false; + } + + /** + * Overrides to provide fading when slide removal is enabled. + */ + @Override + public void onDragFloatView(View floatView, Point position, Point touch) { + + if (mRemoveEnabled && mIsRemoving) { + mPositionX = position.x; + } + } + + /** + * Get the position to start dragging based on the ACTION_DOWN + * MotionEvent. This function simply calls + * {@link #dragHandleHitPosition(MotionEvent)}. Override + * to change drag handle behavior; + * this function is called internally when an ACTION_DOWN + * event is detected. + * + * @param ev The ACTION_DOWN MotionEvent. + * + * @return The list position to drag if a drag-init gesture is + * detected; MISS if unsuccessful. + */ + public int startDragPosition(MotionEvent ev) { + return dragHandleHitPosition(ev); + } + + public int startFlingPosition(MotionEvent ev) { + return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS; + } + + /** + * Checks for the touch of an item's drag handle (specified by + * {@link #setDragHandleId(int)}), and returns that item's position + * if a drag handle touch was detected. + * + * @param ev The ACTION_DOWN MotionEvent. + + * @return The list position of the item whose drag handle was + * touched; MISS if unsuccessful. + */ + public int dragHandleHitPosition(MotionEvent ev) { + return viewIdHitPosition(ev, mDragHandleId); + } + + public int flingHandleHitPosition(MotionEvent ev) { + return viewIdHitPosition(ev, mFlingHandleId); + } + + public int viewIdHitPosition(MotionEvent ev, int id) { + final int x = (int) ev.getX(); + final int y = (int) ev.getY(); + + int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers + + final int numHeaders = mDslv.getHeaderViewsCount(); + final int numFooters = mDslv.getFooterViewsCount(); + final int count = mDslv.getCount(); + + // Log.d("mobeta", "touch down on position " + itemnum); + // We're only interested if the touch was on an + // item that's not a header or footer. + if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders + && touchPos < (count - numFooters)) { + final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition()); + final int rawX = (int) ev.getRawX(); + final int rawY = (int) ev.getRawY(); + + View dragBox = id == 0 ? item : (View) item.findViewById(id); + if (dragBox != null) { + dragBox.getLocationOnScreen(mTempLoc); + + if (rawX > mTempLoc[0] && rawY > mTempLoc[1] && + rawX < mTempLoc[0] + dragBox.getWidth() && + rawY < mTempLoc[1] + dragBox.getHeight()) { + + mItemX = item.getLeft(); + mItemY = item.getTop(); + + return touchPos; + } + } + } + + return MISS; + } + + @Override + public boolean onDown(MotionEvent ev) { + if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) { + mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId); + } + + mHitPos = startDragPosition(ev); + if (mHitPos != MISS && mDragInitMode == ON_DOWN) { + startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY); + } + + mIsRemoving = false; + mCanDrag = true; + mPositionX = 0; + mFlingHitPos = startFlingPosition(ev); + + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + + final int x1 = (int) e1.getX(); + final int y1 = (int) e1.getY(); + final int x2 = (int) e2.getX(); + final int y2 = (int) e2.getY(); + final int deltaX = x2 - mItemX; + final int deltaY = y2 - mItemY; + + if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) { + if (mHitPos != MISS) { + if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) { + startDrag(mHitPos, deltaX, deltaY); + } else if (mDragInitMode != ON_DOWN + && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) { + mIsRemoving = true; + startDrag(mFlingHitPos, deltaX, deltaY); + } + } else if (mFlingHitPos != MISS) { + if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) { + mIsRemoving = true; + startDrag(mFlingHitPos, deltaX, deltaY); + } else if (Math.abs(y2 - y1) > mTouchSlop) { + mCanDrag = false; // if started to scroll the list then + // don't allow sorting nor fling-removing + } + } + } + // return whatever + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + // Log.d("mobeta", "lift listener long pressed"); + if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) { + mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY); + } + } + + // complete the OnGestureListener interface + @Override + public final boolean onFling(MotionEvent e1, + MotionEvent e2, float velocityX, float velocityY) { + return false; + } + + // complete the OnGestureListener interface + @Override + public boolean onSingleTapUp(MotionEvent ev) { + if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) { + if (mClickRemoveHitPos != MISS) { + mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount()); + } + } + return true; + } + + // complete the OnGestureListener interface + @Override + public void onShowPress(MotionEvent ev) { + // do nothing + } + + private GestureDetector.OnGestureListener mFlingRemoveListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + // Log.d("mobeta", "on fling remove called"); + if (mRemoveEnabled && mIsRemoving) { + int w = mDslv.getWidth(); + int minPos = w / 5; + if (velocityX > mFlingSpeed) { + if (mPositionX > -minPos) { + mDslv.stopDragWithVelocity(true, velocityX); + } + } else if (velocityX < -mFlingSpeed) { + if (mPositionX < minPos) { + mDslv.stopDragWithVelocity(true, velocityX); + } + } + mIsRemoving = false; + } + return false; + } + }; + +} diff --git a/src/com/liquid/liquidlounge/preferences/dslv/DragSortItemView.java b/src/com/liquid/liquidlounge/preferences/dslv/DragSortItemView.java new file mode 100644 index 00000000..0cac3bff --- /dev/null +++ b/src/com/liquid/liquidlounge/preferences/dslv/DragSortItemView.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012 Carl Bauer + * Copyright (C) 2014 SlimRoms 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.liquid.liquidlounge.preferences.dslv; + +import android.content.Context; +import android.view.Gravity; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.util.Log; + +/** + * Lightweight ViewGroup that wraps list items obtained from user's + * ListAdapter. ItemView expects a single child that has a definite + * height (i.e. the child's layout height is not MATCH_PARENT). + * The width of + * ItemView will always match the width of its child (that is, + * the width MeasureSpec given to ItemView is passed directly + * to the child, and the ItemView measured width is set to the + * child's measured width). The height of ItemView can be anything; + * the + * + * + * The purpose of this class is to optimize slide + * shuffle animations. + */ +public class DragSortItemView extends ViewGroup { + + private int mGravity = Gravity.TOP; + + public DragSortItemView(Context context) { + super(context); + + // always init with standard ListView layout params + setLayoutParams(new AbsListView.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + //setClipChildren(true); + } + + public void setGravity(int gravity) { + mGravity = gravity; + } + + public int getGravity() { + return mGravity; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final View child = getChildAt(0); + + if (child == null) { + return; + } + + if (mGravity == Gravity.TOP) { + child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight()); + } else { + child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), + getMeasuredWidth(), getMeasuredHeight()); + } + } + + /** + * + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + final View child = getChildAt(0); + if (child == null) { + setMeasuredDimension(0, width); + return; + } + + if (child.isLayoutRequested()) { + // Always let child be as tall as it wants. + measureChild(child, widthMeasureSpec, + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + } + + if (heightMode == MeasureSpec.UNSPECIFIED) { + ViewGroup.LayoutParams lp = getLayoutParams(); + + if (lp.height > 0) { + height = lp.height; + } else { + height = child.getMeasuredHeight(); + } + } + + setMeasuredDimension(width, height); + } + +} diff --git a/src/com/liquid/liquidlounge/preferences/dslv/DragSortListView.java b/src/com/liquid/liquidlounge/preferences/dslv/DragSortListView.java new file mode 100644 index 00000000..96767e67 --- /dev/null +++ b/src/com/liquid/liquidlounge/preferences/dslv/DragSortListView.java @@ -0,0 +1,3067 @@ +/* + * Copyright 2012 Carl Bauer + * Copyright (C) 2014 SlimRoms 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.liquid.liquidlounge.preferences.dslv; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.os.Environment; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.BaseAdapter; +import android.widget.Checkable; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.settings.R; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +/** + * ListView subclass that mediates drag and drop resorting of items. + * + * + * @author heycosmo + * + */ +public class DragSortListView extends ListView { + + /** + * The View that floats above the ListView and represents + * the dragged item. + */ + private View mFloatView; + + /** + * The float View location. First based on touch location + * and given deltaX and deltaY. Then restricted by callback + * to FloatViewManager.onDragFloatView(). Finally restricted + * by bounds of DSLV. + */ + private Point mFloatLoc = new Point(); + + private Point mTouchLoc = new Point(); + + /** + * The middle (in the y-direction) of the floating View. + */ + private int mFloatViewMid; + + /** + * Flag to make sure float View isn't measured twice + */ + private boolean mFloatViewOnMeasured = false; + + /** + * Watch the Adapter for data changes. Cancel a drag if + * coincident with a change. + */ + private DataSetObserver mObserver; + + /** + * Transparency for the floating View (XML attribute). + */ + private float mFloatAlpha = 1.0f; + private float mCurrFloatAlpha = 1.0f; + + /** + * While drag-sorting, the current position of the floating + * View. If dropped, the dragged item will land in this position. + */ + private int mFloatPos; + + /** + * The first expanded ListView position that helps represent + * the drop slot tracking the floating View. + */ + private int mFirstExpPos; + + /** + * The second expanded ListView position that helps represent + * the drop slot tracking the floating View. This can equal + * mFirstExpPos if there is no slide shuffle occurring; otherwise + * it is equal to mFirstExpPos + 1. + */ + private int mSecondExpPos; + + /** + * Flag set if slide shuffling is enabled. + */ + private boolean mAnimate = false; + + /** + * The user dragged from this position. + */ + private int mSrcPos; + + /** + * Offset (in x) within the dragged item at which the user + * picked it up (or first touched down with the digitalis). + */ + private int mDragDeltaX; + + /** + * Offset (in y) within the dragged item at which the user + * picked it up (or first touched down with the digitalis). + */ + private int mDragDeltaY; + + + /** + * The difference (in x) between screen coordinates and coordinates + * in this view. + */ + private int mOffsetX; + + /** + * The difference (in y) between screen coordinates and coordinates + * in this view. + */ + private int mOffsetY; + + /** + * A listener that receives callbacks whenever the floating View + * hovers over a new position. + */ + private DragListener mDragListener; + + /** + * A listener that receives a callback when the floating View + * is dropped. + */ + private DropListener mDropListener; + + /** + * A listener that receives a callback when the floating View + * (or more precisely the originally dragged item) is removed + * by one of the provided gestures. + */ + private RemoveListener mRemoveListener; + + /** + * Enable/Disable item dragging + * + * @attr name dslv:drag_enabled + */ + private boolean mDragEnabled = true; + + /** + * Drag state enum. + */ + private final static int IDLE = 0; + private final static int REMOVING = 1; + private final static int DROPPING = 2; + private final static int STOPPED = 3; + private final static int DRAGGING = 4; + + private int mDragState = IDLE; + + /** + * Height in pixels to which the originally dragged item + * is collapsed during a drag-sort. Currently, this value + * must be greater than zero. + */ + private int mItemHeightCollapsed = 1; + + /** + * Height of the floating View. Stored for the purpose of + * providing the tracking drop slot. + */ + private int mFloatViewHeight; + + /** + * Convenience member. See above. + */ + private int mFloatViewHeightHalf; + + /** + * Save the given width spec for use in measuring children + */ + private int mWidthMeasureSpec = 0; + + /** + * Sample Views ultimately used for calculating the height + * of ListView items that are off-screen. + */ + private View[] mSampleViewTypes = new View[1]; + + /** + * Drag-scroll encapsulator! + */ + private DragScroller mDragScroller; + + /** + * Determines the start of the upward drag-scroll region + * at the top of the ListView. Specified by a fraction + * of the ListView height, thus screen resolution agnostic. + */ + private float mDragUpScrollStartFrac = 1.0f / 3.0f; + + /** + * Determines the start of the downward drag-scroll region + * at the bottom of the ListView. Specified by a fraction + * of the ListView height, thus screen resolution agnostic. + */ + private float mDragDownScrollStartFrac = 1.0f / 3.0f; + + /** + * The following are calculated from the above fracs. + */ + private int mUpScrollStartY; + private int mDownScrollStartY; + private float mDownScrollStartYF; + private float mUpScrollStartYF; + + /** + * Calculated from above above and current ListView height. + */ + private float mDragUpScrollHeight; + + /** + * Calculated from above above and current ListView height. + */ + private float mDragDownScrollHeight; + + /** + * Maximum drag-scroll speed in pixels per ms. Only used with + * default linear drag-scroll profile. + */ + private float mMaxScrollSpeed = 0.5f; + + /** + * Defines the scroll speed during a drag-scroll. User can + * provide their own; this default is a simple linear profile + * where scroll speed increases linearly as the floating View + * nears the top/bottom of the ListView. + */ + private DragScrollProfile mScrollProfile = new DragScrollProfile() { + @Override + public float getSpeed(float w, long t) { + return mMaxScrollSpeed * w; + } + }; + + /** + * Current touch x. + */ + private int mX; + + /** + * Current touch y. + */ + private int mY; + + /** + * Last touch x. + */ + private int mLastX; + + /** + * Last touch y. + */ + private int mLastY; + + /** + * The touch y-coord at which drag started + */ + private int mDragStartY; + + /** + * Drag flag bit. Floating View can move in the positive + * x direction. + */ + public final static int DRAG_POS_X = 0x1; + + /** + * Drag flag bit. Floating View can move in the negative + * x direction. + */ + public final static int DRAG_NEG_X = 0x2; + + /** + * Drag flag bit. Floating View can move in the positive + * y direction. This is subtle. What this actually means is + * that, if enabled, the floating View can be dragged below its starting + * position. Remove in favor of upper-bounding item position? + */ + public final static int DRAG_POS_Y = 0x4; + + /** + * Drag flag bit. Floating View can move in the negative + * y direction. This is subtle. What this actually means is + * that the floating View can be dragged above its starting + * position. Remove in favor of lower-bounding item position? + */ + public final static int DRAG_NEG_Y = 0x8; + + /** + * Flags that determine limits on the motion of the + * floating View. See flags above. + */ + private int mDragFlags = 0; + + /** + * Last call to an on*TouchEvent was a call to + * onInterceptTouchEvent. + */ + private boolean mLastCallWasIntercept = false; + + /** + * A touch event is in progress. + */ + private boolean mInTouchEvent = false; + + /** + * Let the user customize the floating View. + */ + private FloatViewManager mFloatViewManager = null; + + /** + * Given to ListView to cancel its action when a drag-sort + * begins. + */ + private MotionEvent mCancelEvent; + + /** + * Enum telling where to cancel the ListView action when a + * drag-sort begins + */ + private static final int NO_CANCEL = 0; + private static final int ON_TOUCH_EVENT = 1; + private static final int ON_INTERCEPT_TOUCH_EVENT = 2; + + /** + * Where to cancel the ListView action when a + * drag-sort begins + */ + private int mCancelMethod = NO_CANCEL; + + /** + * Determines when a slide shuffle animation starts. That is, + * defines how close to the edge of the drop slot the floating + * View must be to initiate the slide. + */ + private float mSlideRegionFrac = 0.25f; + + /** + * Number between 0 and 1 indicating the relative location of + * a sliding item (only used if drag-sort animations + * are turned on). Nearly 1 means the item is + * at the top of the slide region (nearly full blank item + * is directly below). + */ + private float mSlideFrac = 0.0f; + + /** + * Wraps the user-provided ListAdapter. This is used to wrap each + * item View given by the user inside another View (currenly + * a RelativeLayout) which + * expands and collapses to simulate the item shuffling. + */ + private AdapterWrapper mAdapterWrapper; + + /** + * Turn on custom debugger. + */ + private boolean mTrackDragSort = false; + + /** + * Debugging class. + */ + private DragSortTracker mDragSortTracker; + + /** + * Needed for adjusting item heights from within layoutChildren + */ + private boolean mBlockLayoutRequests = false; + + /** + * Set to true when a down event happens during drag sort; + * for example, when drag finish animations are + * playing. + */ + private boolean mIgnoreTouchEvent = false; + + /** + * Caches DragSortItemView child heights. Sometimes DSLV has to + * know the height of an offscreen item. Since ListView virtualizes + * these, DSLV must get the item from the ListAdapter to obtain + * its height. That process can be expensive, but often the same + * offscreen item will be requested many times in a row. Once an + * offscreen item height is calculated, we cache it in this guy. + * Actually, we cache the height of the child of the + * DragSortItemView since the item height changes often during a + * drag-sort. + */ + private static final int sCacheSize = 3; + private HeightCache mChildHeightCache = new HeightCache(sCacheSize); + + private RemoveAnimator mRemoveAnimator; + + private LiftAnimator mLiftAnimator; + + private DropAnimator mDropAnimator; + + private boolean mUseRemoveVelocity; + private float mRemoveVelocityX = 0; + + public DragSortListView(Context context, AttributeSet attrs) { + super(context, attrs); + + int defaultDuration = 150; + int removeAnimDuration = defaultDuration; // ms + int dropAnimDuration = defaultDuration; // ms + + if (attrs != null) { + TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.DragSortListView, 0, 0); + + mItemHeightCollapsed = Math.max(1, a.getDimensionPixelSize( + R.styleable.DragSortListView_collapsedHeight, 1)); + + mTrackDragSort = a.getBoolean( + R.styleable.DragSortListView_trackDragSort, false); + + if (mTrackDragSort) { + mDragSortTracker = new DragSortTracker(); + } + + // alpha between 0 and 255, 0=transparent, 255=opaque + mFloatAlpha = a.getFloat(R.styleable.DragSortListView_floatAlpha, mFloatAlpha); + mCurrFloatAlpha = mFloatAlpha; + + mDragEnabled = a.getBoolean(R.styleable.DragSortListView_dragEnabled, mDragEnabled); + + mSlideRegionFrac = Math.max(0.0f, + Math.min(1.0f, 1.0f - a.getFloat( + R.styleable.DragSortListView_slideShuffleSpeed, + 0.75f))); + + mAnimate = mSlideRegionFrac > 0.0f; + + float frac = a.getFloat( + R.styleable.DragSortListView_dragScrollStart, + mDragUpScrollStartFrac); + + setDragScrollStart(frac); + + mMaxScrollSpeed = a.getFloat( + R.styleable.DragSortListView_maxDragScrollSpeed, + mMaxScrollSpeed); + + removeAnimDuration = a.getInt( + R.styleable.DragSortListView_removeAnimationDuration, + removeAnimDuration); + + dropAnimDuration = a.getInt( + R.styleable.DragSortListView_dropAnimationDuration, + dropAnimDuration); + + boolean useDefault = a.getBoolean( + R.styleable.DragSortListView_useDefaultController, + true); + + if (useDefault) { + boolean removeEnabled = a.getBoolean( + R.styleable.DragSortListView_removeEnabled, + false); + int removeMode = a.getInt( + R.styleable.DragSortListView_removeMode, + DragSortController.FLING_REMOVE); + boolean sortEnabled = a.getBoolean( + R.styleable.DragSortListView_sortEnabled, + true); + int dragInitMode = a.getInt( + R.styleable.DragSortListView_dragStartMode, + DragSortController.ON_DOWN); + int dragHandleId = a.getResourceId( + R.styleable.DragSortListView_dragHandleId, + 0); + int flingHandleId = a.getResourceId( + R.styleable.DragSortListView_flingHandleId, + 0); + int clickRemoveId = a.getResourceId( + R.styleable.DragSortListView_clickRemoveId, + 0); + int bgColor = a.getColor( + R.styleable.DragSortListView_floatBackgroundColor, + Color.BLACK); + + DragSortController controller = new DragSortController( + this, dragHandleId, dragInitMode, removeMode, + clickRemoveId, flingHandleId); + controller.setRemoveEnabled(removeEnabled); + controller.setSortEnabled(sortEnabled); + controller.setBackgroundColor(bgColor); + + mFloatViewManager = controller; + setOnTouchListener(controller); + } + + a.recycle(); + } + + mDragScroller = new DragScroller(); + + float smoothness = 0.5f; + if (removeAnimDuration > 0) { + mRemoveAnimator = new RemoveAnimator(smoothness, removeAnimDuration); + } + // mLiftAnimator = new LiftAnimator(smoothness, 100); + if (dropAnimDuration > 0) { + mDropAnimator = new DropAnimator(smoothness, dropAnimDuration); + } + + mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f, + 0f, 0, 0); + + // construct the dataset observer + mObserver = new DataSetObserver() { + private void cancel() { + if (mDragState == DRAGGING) { + cancelDrag(); + } + } + + @Override + public void onChanged() { + cancel(); + } + + @Override + public void onInvalidated() { + cancel(); + } + }; + } + + /** + * Usually called from a FloatViewManager. The float alpha + * will be reset to the xml-defined value every time a drag + * is stopped. + */ + public void setFloatAlpha(float alpha) { + mCurrFloatAlpha = alpha; + } + + public float getFloatAlpha() { + return mCurrFloatAlpha; + } + + /** + * Set maximum drag scroll speed in positions/second. Only applies + * if using default ScrollSpeedProfile. + * + * @param max Maximum scroll speed. + */ + public void setMaxScrollSpeed(float max) { + mMaxScrollSpeed = max; + } + + /** + * For each DragSortListView Listener interface implemented by + * adapter, this method calls the appropriate + * set*Listener method with adapter as the argument. + * + * @param adapter The ListAdapter providing data to back + * DragSortListView. + * + * @see android.widget.ListView#setAdapter(android.widget.ListAdapter) + */ + @Override + public void setAdapter(ListAdapter adapter) { + if (adapter != null) { + mAdapterWrapper = new AdapterWrapper(adapter); + adapter.registerDataSetObserver(mObserver); + + if (adapter instanceof DropListener) { + setDropListener((DropListener) adapter); + } + if (adapter instanceof DragListener) { + setDragListener((DragListener) adapter); + } + if (adapter instanceof RemoveListener) { + setRemoveListener((RemoveListener) adapter); + } + } else { + mAdapterWrapper = null; + } + + super.setAdapter(mAdapterWrapper); + } + + /** + * As opposed to {@link ListView#getAdapter()}, which returns + * a heavily wrapped ListAdapter (DragSortListView wraps the + * input ListAdapter {\emph and} ListView wraps the wrapped one). + * + * @return The ListAdapter set as the argument of {@link setAdapter()} + */ + public ListAdapter getInputAdapter() { + if (mAdapterWrapper == null) { + return null; + } else { + return mAdapterWrapper.getAdapter(); + } + } + + private class AdapterWrapper extends BaseAdapter { + private ListAdapter mAdapter; + + public AdapterWrapper(ListAdapter adapter) { + super(); + mAdapter = adapter; + + mAdapter.registerDataSetObserver(new DataSetObserver() { + public void onChanged() { + notifyDataSetChanged(); + } + + public void onInvalidated() { + notifyDataSetInvalidated(); + } + }); + } + + public ListAdapter getAdapter() { + return mAdapter; + } + + @Override + public long getItemId(int position) { + return mAdapter.getItemId(position); + } + + @Override + public Object getItem(int position) { + return mAdapter.getItem(position); + } + + @Override + public int getCount() { + return mAdapter.getCount(); + } + + @Override + public boolean areAllItemsEnabled() { + return mAdapter.areAllItemsEnabled(); + } + + @Override + public boolean isEnabled(int position) { + return mAdapter.isEnabled(position); + } + + @Override + public int getItemViewType(int position) { + return mAdapter.getItemViewType(position); + } + + @Override + public int getViewTypeCount() { + return mAdapter.getViewTypeCount(); + } + + @Override + public boolean hasStableIds() { + return mAdapter.hasStableIds(); + } + + @Override + public boolean isEmpty() { + return mAdapter.isEmpty(); + } + + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + DragSortItemView v; + View child; + // Log.d("mobeta", + // "getView: position="+position+" convertView="+convertView); + if (convertView != null) { + v = (DragSortItemView) convertView; + View oldChild = v.getChildAt(0); + + child = mAdapter.getView(position, oldChild, DragSortListView.this); + if (child != oldChild) { + // shouldn't get here if user is reusing convertViews + // properly + if (oldChild != null) { + v.removeViewAt(0); + } + v.addView(child); + } + } else { + child = mAdapter.getView(position, null, DragSortListView.this); + v = new DragSortItemView(getContext()); + v.setLayoutParams(new AbsListView.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + v.addView(child); + } + + // Set the correct item height given drag state; passed + // View needs to be measured if measurement is required. + adjustItem(position + getHeaderViewsCount(), v, true); + + return v; + } + } + + private void drawDivider(int expPosition, Canvas canvas) { + + final Drawable divider = getDivider(); + final int dividerHeight = getDividerHeight(); + // Log.d("mobeta", "div="+divider+" divH="+dividerHeight); + + if (divider != null && dividerHeight != 0) { + final ViewGroup expItem = (ViewGroup) getChildAt(expPosition + - getFirstVisiblePosition()); + if (expItem != null) { + final int l = getPaddingLeft(); + final int r = getWidth() - getPaddingRight(); + final int t; + final int b; + + final int childHeight = expItem.getChildAt(0).getHeight(); + + if (expPosition > mSrcPos) { + t = expItem.getTop() + childHeight; + b = t + dividerHeight; + } else { + b = expItem.getBottom() - childHeight; + t = b - dividerHeight; + } + // Log.d("mobeta", "l="+l+" t="+t+" r="+r+" b="+b); + + // Have to clip to support ColorDrawable on <= Gingerbread + canvas.save(); + canvas.clipRect(l, t, r, b); + divider.setBounds(l, t, r, b); + divider.draw(canvas); + canvas.restore(); + } + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + if (mDragState != IDLE) { + // draw the divider over the expanded item + if (mFirstExpPos != mSrcPos) { + drawDivider(mFirstExpPos, canvas); + } + if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) { + drawDivider(mSecondExpPos, canvas); + } + } + + if (mFloatView != null) { + // draw the float view over everything + final int w = mFloatView.getWidth(); + final int h = mFloatView.getHeight(); + + int x = mFloatLoc.x; + + int width = getWidth(); + if (x < 0) + x = -x; + float alphaMod; + if (x < width) { + alphaMod = ((float) (width - x)) / ((float) width); + alphaMod *= alphaMod; + } else { + alphaMod = 0; + } + + final int alpha = (int) (255f * mCurrFloatAlpha * alphaMod); + + canvas.save(); + // Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds()); + canvas.translate(mFloatLoc.x, mFloatLoc.y); + canvas.clipRect(0, 0, w, h); + + // Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds()); + canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG); + mFloatView.draw(canvas); + canvas.restore(); + canvas.restore(); + } + } + + private int getItemHeight(int position) { + View v = getChildAt(position - getFirstVisiblePosition()); + + if (v != null) { + // item is onscreen, just get the height of the View + return v.getHeight(); + } else { + // item is offscreen. get child height and calculate + // item height based on current shuffle state + return calcItemHeight(position, getChildHeight(position)); + } + } + + private void printPosData() { + Log.d("mobeta", "mSrcPos=" + mSrcPos + " mFirstExpPos=" + mFirstExpPos + " mSecondExpPos=" + + mSecondExpPos); + } + + private class HeightCache { + + private SparseIntArray mMap; + private ArrayList mOrder; + private int mMaxSize; + + public HeightCache(int size) { + mMap = new SparseIntArray(size); + mOrder = new ArrayList(size); + mMaxSize = size; + } + + /** + * Add item height at position if doesn't already exist. + */ + public void add(int position, int height) { + int currHeight = mMap.get(position, -1); + if (currHeight != height) { + if (currHeight == -1) { + if (mMap.size() == mMaxSize) { + // remove oldest entry + mMap.delete(mOrder.remove(0)); + } + } else { + // move position to newest slot + mOrder.remove((Integer) position); + } + mMap.put(position, height); + mOrder.add(position); + } + } + + public int get(int position) { + return mMap.get(position, -1); + } + + public void clear() { + mMap.clear(); + mOrder.clear(); + } + + } + + /** + * Get the shuffle edge for item at position when top of + * item is at y-coord top. Assumes that current item heights + * are consistent with current float view location and + * thus expanded positions and slide fraction. i.e. Should not be + * called between update of expanded positions/slide fraction + * and layoutChildren. + * + * @param position + * @param top + * @param height Height of item at position. If -1, this function + * calculates this height. + * + * @return Shuffle line between position-1 and position (for + * the given view of the list; that is, for when top of item at + * position has y-coord of given `top`). If + * floating View (treated as horizontal line) is dropped + * immediately above this line, it lands in position-1. If + * dropped immediately below this line, it lands in position. + */ + private int getShuffleEdge(int position, int top) { + + final int numHeaders = getHeaderViewsCount(); + final int numFooters = getFooterViewsCount(); + + // shuffle edges are defined between items that can be + // dragged; there are N-1 of them if there are N draggable + // items. + + if (position <= numHeaders || (position >= getCount() - numFooters)) { + return top; + } + + int divHeight = getDividerHeight(); + + int edge; + + int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed; + int childHeight = getChildHeight(position); + int itemHeight = getItemHeight(position); + + // first calculate top of item given that floating View is + // centered over src position + int otop = top; + if (mSecondExpPos <= mSrcPos) { + // items are expanded on and/or above the source position + + if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) { + if (position == mSrcPos) { + otop = top + itemHeight - mFloatViewHeight; + } else { + int blankHeight = itemHeight - childHeight; + otop = top + blankHeight - maxBlankHeight; + } + } else if (position > mSecondExpPos && position <= mSrcPos) { + otop = top - maxBlankHeight; + } + + } else { + // items are expanded on and/or below the source position + + if (position > mSrcPos && position <= mFirstExpPos) { + otop = top + maxBlankHeight; + } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) { + int blankHeight = itemHeight - childHeight; + otop = top + blankHeight; + } + } + + // otop is set + if (position <= mSrcPos) { + edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2; + } else { + edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2; + } + + return edge; + } + + private boolean updatePositions() { + + final int first = getFirstVisiblePosition(); + int startPos = mFirstExpPos; + View startView = getChildAt(startPos - first); + + if (startView == null) { + startPos = first + getChildCount() / 2; + startView = getChildAt(startPos - first); + } + int startTop = startView.getTop(); + + int itemHeight = startView.getHeight(); + + int edge = getShuffleEdge(startPos, startTop); + int lastEdge = edge; + + int divHeight = getDividerHeight(); + + // Log.d("mobeta", "float mid="+mFloatViewMid); + + int itemPos = startPos; + int itemTop = startTop; + if (mFloatViewMid < edge) { + // scanning up for float position + // Log.d("mobeta", " edge="+edge); + while (itemPos >= 0) { + itemPos--; + itemHeight = getItemHeight(itemPos); + + if (itemPos == 0) { + edge = itemTop - divHeight - itemHeight; + break; + } + + itemTop -= itemHeight + divHeight; + edge = getShuffleEdge(itemPos, itemTop); + // Log.d("mobeta", " edge="+edge); + + if (mFloatViewMid >= edge) { + break; + } + + lastEdge = edge; + } + } else { + // scanning down for float position + // Log.d("mobeta", " edge="+edge); + final int count = getCount(); + while (itemPos < count) { + if (itemPos == count - 1) { + edge = itemTop + divHeight + itemHeight; + break; + } + + itemTop += divHeight + itemHeight; + itemHeight = getItemHeight(itemPos + 1); + edge = getShuffleEdge(itemPos + 1, itemTop); + // Log.d("mobeta", " edge="+edge); + + // test for hit + if (mFloatViewMid < edge) { + break; + } + + lastEdge = edge; + itemPos++; + } + } + + final int numHeaders = getHeaderViewsCount(); + final int numFooters = getFooterViewsCount(); + + boolean updated = false; + + int oldFirstExpPos = mFirstExpPos; + int oldSecondExpPos = mSecondExpPos; + float oldSlideFrac = mSlideFrac; + + if (mAnimate) { + int edgeToEdge = Math.abs(edge - lastEdge); + + int edgeTop, edgeBottom; + if (mFloatViewMid < edge) { + edgeBottom = edge; + edgeTop = lastEdge; + } else { + edgeTop = edge; + edgeBottom = lastEdge; + } + // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom); + + int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge); + float slideRgnHeightF = (float) slideRgnHeight; + int slideEdgeTop = edgeTop + slideRgnHeight; + int slideEdgeBottom = edgeBottom - slideRgnHeight; + + // Three regions + if (mFloatViewMid < slideEdgeTop) { + mFirstExpPos = itemPos - 1; + mSecondExpPos = itemPos; + mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF; + // Log.d("mobeta", + // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac); + } else if (mFloatViewMid < slideEdgeBottom) { + mFirstExpPos = itemPos; + mSecondExpPos = itemPos; + } else { + mFirstExpPos = itemPos; + mSecondExpPos = itemPos + 1; + mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid)) + / slideRgnHeightF); + // Log.d("mobeta", + // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac); + } + + } else { + mFirstExpPos = itemPos; + mSecondExpPos = itemPos; + } + + // correct for headers and footers + if (mFirstExpPos < numHeaders) { + itemPos = numHeaders; + mFirstExpPos = itemPos; + mSecondExpPos = itemPos; + } else if (mSecondExpPos >= getCount() - numFooters) { + itemPos = getCount() - numFooters - 1; + mFirstExpPos = itemPos; + mSecondExpPos = itemPos; + } + + if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos + || mSlideFrac != oldSlideFrac) { + updated = true; + } + + if (itemPos != mFloatPos) { + if (mDragListener != null) { + mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders); + } + + mFloatPos = itemPos; + updated = true; + } + + return updated; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mTrackDragSort) { + mDragSortTracker.appendState(); + } + } + + private class SmoothAnimator implements Runnable { + protected long mStartTime; + + private float mDurationF; + + private float mAlpha; + private float mA, mB, mC, mD; + + private boolean mCanceled; + + public SmoothAnimator(float smoothness, int duration) { + mAlpha = smoothness; + mDurationF = (float) duration; + mA = mD = 1f / (2f * mAlpha * (1f - mAlpha)); + mB = mAlpha / (2f * (mAlpha - 1f)); + mC = 1f / (1f - mAlpha); + } + + public float transform(float frac) { + if (frac < mAlpha) { + return mA * frac * frac; + } else if (frac < 1f - mAlpha) { + return mB + mC * frac; + } else { + return 1f - mD * (frac - 1f) * (frac - 1f); + } + } + + public void start() { + mStartTime = SystemClock.uptimeMillis(); + mCanceled = false; + onStart(); + post(this); + } + + public void cancel() { + mCanceled = true; + } + + public void onStart() { + // stub + } + + public void onUpdate(float frac, float smoothFrac) { + // stub + } + + public void onStop() { + // stub + } + + @Override + public void run() { + if (mCanceled) { + return; + } + + float fraction = ((float) (SystemClock.uptimeMillis() - mStartTime)) / mDurationF; + + if (fraction >= 1f) { + onUpdate(1f, 1f); + onStop(); + } else { + onUpdate(fraction, transform(fraction)); + post(this); + } + } + } + + /** + * Centers floating View under touch point. + */ + private class LiftAnimator extends SmoothAnimator { + + private float mInitDragDeltaY; + private float mFinalDragDeltaY; + + public LiftAnimator(float smoothness, int duration) { + super(smoothness, duration); + } + + @Override + public void onStart() { + mInitDragDeltaY = mDragDeltaY; + mFinalDragDeltaY = mFloatViewHeightHalf; + } + + @Override + public void onUpdate(float frac, float smoothFrac) { + if (mDragState != DRAGGING) { + cancel(); + } else { + mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac) + * mInitDragDeltaY); + mFloatLoc.y = mY - mDragDeltaY; + doDragFloatView(true); + } + } + } + + /** + * Centers floating View over drop slot before destroying. + */ + private class DropAnimator extends SmoothAnimator { + + private int mDropPos; + private int srcPos; + private float mInitDeltaY; + private float mInitDeltaX; + + public DropAnimator(float smoothness, int duration) { + super(smoothness, duration); + } + + @Override + public void onStart() { + mDropPos = mFloatPos; + srcPos = mSrcPos; + mDragState = DROPPING; + mInitDeltaY = mFloatLoc.y - getTargetY(); + mInitDeltaX = mFloatLoc.x - getPaddingLeft(); + } + + private int getTargetY() { + final int first = getFirstVisiblePosition(); + final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2; + View v = getChildAt(mDropPos - first); + int targetY = -1; + if (v != null) { + if (mDropPos == srcPos) { + targetY = v.getTop(); + } else if (mDropPos < srcPos) { + // expanded down + targetY = v.getTop() - otherAdjust; + } else { + // expanded up + targetY = v.getBottom() + otherAdjust - mFloatViewHeight; + } + } else { + // drop position is not on screen?? no animation + cancel(); + } + + return targetY; + } + + @Override + public void onUpdate(float frac, float smoothFrac) { + final int targetY = getTargetY(); + final int targetX = getPaddingLeft(); + final float deltaY = mFloatLoc.y - targetY; + final float deltaX = mFloatLoc.x - targetX; + final float f = 1f - smoothFrac; + if (f < Math.abs(deltaY / mInitDeltaY) || f < Math.abs(deltaX / mInitDeltaX)) { + mFloatLoc.y = targetY + (int) (mInitDeltaY * f); + mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f); + doDragFloatView(true); + } + } + + @Override + public void onStop() { + dropFloatView(); + } + + } + + /** + * Collapses expanded items. + */ + private class RemoveAnimator extends SmoothAnimator { + + private float mFloatLocX; + private float mFirstStartBlank; + private float mSecondStartBlank; + + private int mFirstChildHeight = -1; + private int mSecondChildHeight = -1; + + private int mFirstPos; + private int mSecondPos; + private int srcPos; + + public RemoveAnimator(float smoothness, int duration) { + super(smoothness, duration); + } + + @Override + public void onStart() { + mFirstChildHeight = -1; + mSecondChildHeight = -1; + mFirstPos = mFirstExpPos; + mSecondPos = mSecondExpPos; + srcPos = mSrcPos; + mDragState = REMOVING; + + mFloatLocX = mFloatLoc.x; + if (mUseRemoveVelocity) { + float minVelocity = 2f * getWidth(); + if (mRemoveVelocityX == 0) { + mRemoveVelocityX = (mFloatLocX < 0 ? -1 : 1) * minVelocity; + } else { + minVelocity *= 2; + if (mRemoveVelocityX < 0 && mRemoveVelocityX > -minVelocity) + mRemoveVelocityX = -minVelocity; + else if (mRemoveVelocityX > 0 && mRemoveVelocityX < minVelocity) + mRemoveVelocityX = minVelocity; + } + } else { + destroyFloatView(); + } + } + + @Override + public void onUpdate(float frac, float smoothFrac) { + float f = 1f - smoothFrac; + + final int firstVis = getFirstVisiblePosition(); + View item = getChildAt(mFirstPos - firstVis); + ViewGroup.LayoutParams lp; + int blank; + + if (mUseRemoveVelocity) { + float dt = (float) (SystemClock.uptimeMillis() - mStartTime) / 1000; + if (dt == 0) + return; + float dx = mRemoveVelocityX * dt; + int w = getWidth(); + mRemoveVelocityX += (mRemoveVelocityX > 0 ? 1 : -1) * dt * w; + mFloatLocX += dx; + mFloatLoc.x = (int) mFloatLocX; + if (mFloatLocX < w && mFloatLocX > -w) { + mStartTime = SystemClock.uptimeMillis(); + doDragFloatView(true); + return; + } + } + + if (item != null) { + if (mFirstChildHeight == -1) { + mFirstChildHeight = getChildHeight(mFirstPos, item, false); + mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight); + } + blank = Math.max((int) (f * mFirstStartBlank), 1); + lp = item.getLayoutParams(); + lp.height = mFirstChildHeight + blank; + item.setLayoutParams(lp); + } + if (mSecondPos != mFirstPos) { + item = getChildAt(mSecondPos - firstVis); + if (item != null) { + if (mSecondChildHeight == -1) { + mSecondChildHeight = getChildHeight(mSecondPos, item, false); + mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight); + } + blank = Math.max((int) (f * mSecondStartBlank), 1); + lp = item.getLayoutParams(); + lp.height = mSecondChildHeight + blank; + item.setLayoutParams(lp); + } + } + } + + @Override + public void onStop() { + doRemoveItem(); + } + } + + public void removeItem(int which) { + + mUseRemoveVelocity = false; + removeItem(which, 0); + } + + /** + * Removes an item from the list and animates the removal. + * + * @param which Position to remove (NOTE: headers/footers ignored! + * this is a position in your input ListAdapter). + * @param velocityX + */ + public void removeItem(int which, float velocityX) { + if (mDragState == IDLE || mDragState == DRAGGING) { + + if (mDragState == IDLE) { + // called from outside drag-sort + mSrcPos = getHeaderViewsCount() + which; + mFirstExpPos = mSrcPos; + mSecondExpPos = mSrcPos; + mFloatPos = mSrcPos; + View v = getChildAt(mSrcPos - getFirstVisiblePosition()); + if (v != null) { + v.setVisibility(View.INVISIBLE); + } + } + + mDragState = REMOVING; + mRemoveVelocityX = velocityX; + + if (mInTouchEvent) { + switch (mCancelMethod) { + case ON_TOUCH_EVENT: + super.onTouchEvent(mCancelEvent); + break; + case ON_INTERCEPT_TOUCH_EVENT: + super.onInterceptTouchEvent(mCancelEvent); + break; + } + } + + if (mRemoveAnimator != null) { + mRemoveAnimator.start(); + } else { + doRemoveItem(which); + } + } + } + + /** + * Move an item, bypassing the drag-sort process. Simply calls + * through to {@link DropListener#drop(int, int)}. + * + * @param from Position to move (NOTE: headers/footers ignored! + * this is a position in your input ListAdapter). + * @param to Target position (NOTE: headers/footers ignored! + * this is a position in your input ListAdapter). + */ + public void moveItem(int from, int to) { + if (mDropListener != null) { + final int count = getInputAdapter().getCount(); + if (from >= 0 && from < count && to >= 0 && to < count) { + mDropListener.drop(from, to); + } + } + } + + /** + * Cancel a drag. Calls {@link #stopDrag(boolean, boolean)} with + * true as the first argument. + */ + public void cancelDrag() { + if (mDragState == DRAGGING) { + mDragScroller.stopScrolling(true); + destroyFloatView(); + clearPositions(); + adjustAllItems(); + + if (mInTouchEvent) { + mDragState = STOPPED; + } else { + mDragState = IDLE; + } + } + } + + private void clearPositions() { + mSrcPos = -1; + mFirstExpPos = -1; + mSecondExpPos = -1; + mFloatPos = -1; + } + + private void dropFloatView() { + // must set to avoid cancelDrag being called from the + // DataSetObserver + mDragState = DROPPING; + + if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) { + final int numHeaders = getHeaderViewsCount(); + mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders); + } + + destroyFloatView(); + + adjustOnReorder(); + clearPositions(); + adjustAllItems(); + + // now the drag is done + if (mInTouchEvent) { + mDragState = STOPPED; + } else { + mDragState = IDLE; + } + } + + private void doRemoveItem() { + doRemoveItem(mSrcPos - getHeaderViewsCount()); + } + + /** + * Removes dragged item from the list. Calls RemoveListener. + */ + private void doRemoveItem(int which) { + // must set to avoid cancelDrag being called from the + // DataSetObserver + mDragState = REMOVING; + + // end it + if (mRemoveListener != null) { + mRemoveListener.remove(which); + } + + destroyFloatView(); + + adjustOnReorder(); + clearPositions(); + + // now the drag is done + if (mInTouchEvent) { + mDragState = STOPPED; + } else { + mDragState = IDLE; + } + } + + private void adjustOnReorder() { + final int firstPos = getFirstVisiblePosition(); + // Log.d("mobeta", "first="+firstPos+" src="+mSrcPos); + if (mSrcPos < firstPos) { + // collapsed src item is off screen; + // adjust the scroll after item heights have been fixed + View v = getChildAt(0); + int top = 0; + if (v != null) { + top = v.getTop(); + } + // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight); + setSelectionFromTop(firstPos - 1, top - getPaddingTop()); + } + } + + /** + * Stop a drag in progress. Pass true if you would + * like to remove the dragged item from the list. + * + * @param remove Remove the dragged item from the list. Calls + * a registered RemoveListener, if one exists. Otherwise, calls + * the DropListener, if one exists. + * + * @return True if the stop was successful. False if there is + * no floating View. + */ + public boolean stopDrag(boolean remove) { + mUseRemoveVelocity = false; + return stopDrag(remove, 0); + } + + public boolean stopDragWithVelocity(boolean remove, float velocityX) { + + mUseRemoveVelocity = true; + return stopDrag(remove, velocityX); + } + + public boolean stopDrag(boolean remove, float velocityX) { + if (mFloatView != null) { + mDragScroller.stopScrolling(true); + + if (remove) { + removeItem(mSrcPos - getHeaderViewsCount(), velocityX); + } else { + if (mDropAnimator != null) { + mDropAnimator.start(); + } else { + dropFloatView(); + } + } + + if (mTrackDragSort) { + mDragSortTracker.stopTracking(); + } + + return true; + } else { + // stop failed + return false; + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mIgnoreTouchEvent) { + mIgnoreTouchEvent = false; + return false; + } + + if (!mDragEnabled) { + return super.onTouchEvent(ev); + } + + boolean more = false; + + boolean lastCallWasIntercept = mLastCallWasIntercept; + mLastCallWasIntercept = false; + + if (!lastCallWasIntercept) { + saveTouchCoords(ev); + } + + // if (mFloatView != null) { + if (mDragState == DRAGGING) { + onDragTouchEvent(ev); + more = true; // give us more! + } else { + // what if float view is null b/c we dropped in middle + // of drag touch event? + + // if (mDragState != STOPPED) { + if (mDragState == IDLE) { + if (super.onTouchEvent(ev)) { + more = true; + } + } + + int action = ev.getAction() & MotionEvent.ACTION_MASK; + + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + doActionUpOrCancel(); + break; + default: + if (more) { + mCancelMethod = ON_TOUCH_EVENT; + } + } + } + + return more; + } + + private void doActionUpOrCancel() { + mCancelMethod = NO_CANCEL; + mInTouchEvent = false; + if (mDragState == STOPPED) { + mDragState = IDLE; + } + mCurrFloatAlpha = mFloatAlpha; + mListViewIntercepted = false; + mChildHeightCache.clear(); + } + + private void saveTouchCoords(MotionEvent ev) { + int action = ev.getAction() & MotionEvent.ACTION_MASK; + if (action != MotionEvent.ACTION_DOWN) { + mLastX = mX; + mLastY = mY; + } + mX = (int) ev.getX(); + mY = (int) ev.getY(); + if (action == MotionEvent.ACTION_DOWN) { + mLastX = mX; + mLastY = mY; + } + mOffsetX = (int) ev.getRawX() - mX; + mOffsetY = (int) ev.getRawY() - mY; + } + + public boolean listViewIntercepted() { + return mListViewIntercepted; + } + + private boolean mListViewIntercepted = false; + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!mDragEnabled) { + return super.onInterceptTouchEvent(ev); + } + + saveTouchCoords(ev); + mLastCallWasIntercept = true; + + int action = ev.getAction() & MotionEvent.ACTION_MASK; + + if (action == MotionEvent.ACTION_DOWN) { + if (mDragState != IDLE) { + // intercept and ignore + mIgnoreTouchEvent = true; + return true; + } + mInTouchEvent = true; + } + + boolean intercept = false; + + // the following deals with calls to super.onInterceptTouchEvent + if (mFloatView != null) { + // super's touch event canceled in startDrag + intercept = true; + } else { + if (super.onInterceptTouchEvent(ev)) { + mListViewIntercepted = true; + intercept = true; + } + + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + doActionUpOrCancel(); + break; + default: + if (intercept) { + mCancelMethod = ON_TOUCH_EVENT; + } else { + mCancelMethod = ON_INTERCEPT_TOUCH_EVENT; + } + } + } + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + mInTouchEvent = false; + } + + return intercept; + } + + /** + * Set the width of each drag scroll region by specifying + * a fraction of the ListView height. + * + * @param heightFraction Fraction of ListView height. Capped at + * 0.5f. + * + */ + public void setDragScrollStart(float heightFraction) { + setDragScrollStarts(heightFraction, heightFraction); + } + + /** + * Set the width of each drag scroll region by specifying + * a fraction of the ListView height. + * + * @param upperFrac Fraction of ListView height for up-scroll bound. + * Capped at 0.5f. + * @param lowerFrac Fraction of ListView height for down-scroll bound. + * Capped at 0.5f. + * + */ + public void setDragScrollStarts(float upperFrac, float lowerFrac) { + if (lowerFrac > 0.5f) { + mDragDownScrollStartFrac = 0.5f; + } else { + mDragDownScrollStartFrac = lowerFrac; + } + + if (upperFrac > 0.5f) { + mDragUpScrollStartFrac = 0.5f; + } else { + mDragUpScrollStartFrac = upperFrac; + } + + if (getHeight() != 0) { + updateScrollStarts(); + } + } + + private void continueDrag(int x, int y) { + + // proposed position + mFloatLoc.x = x - mDragDeltaX; + mFloatLoc.y = y - mDragDeltaY; + + doDragFloatView(true); + + int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf); + int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf); + + // get the current scroll direction + int currentScrollDir = mDragScroller.getScrollDir(); + + if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) { + // dragged down, it is below the down scroll start and it is not + // scrolling up + + if (currentScrollDir != DragScroller.STOP) { + // moved directly from up scroll to down scroll + mDragScroller.stopScrolling(true); + } + + // start scrolling down + mDragScroller.startScrolling(DragScroller.DOWN); + } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) { + // dragged up, it is above the up scroll start and it is not + // scrolling up + + if (currentScrollDir != DragScroller.STOP) { + // moved directly from down scroll to up scroll + mDragScroller.stopScrolling(true); + } + + // start scrolling up + mDragScroller.startScrolling(DragScroller.UP); + } + else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY + && mDragScroller.isScrolling()) { + // not in the upper nor in the lower drag-scroll regions but it is + // still scrolling + + mDragScroller.stopScrolling(true); + } + } + + private void updateScrollStarts() { + final int padTop = getPaddingTop(); + final int listHeight = getHeight() - padTop - getPaddingBottom(); + float heightF = (float) listHeight; + + mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF; + mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF; + + mUpScrollStartY = (int) mUpScrollStartYF; + mDownScrollStartY = (int) mDownScrollStartYF; + + mDragUpScrollHeight = mUpScrollStartYF - padTop; + mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateScrollStarts(); + } + + private void adjustAllItems() { + final int first = getFirstVisiblePosition(); + final int last = getLastVisiblePosition(); + + int begin = Math.max(0, getHeaderViewsCount() - first); + int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first); + + for (int i = begin; i <= end; ++i) { + View v = getChildAt(i); + if (v != null) { + adjustItem(first + i, v, false); + } + } + } + + private void adjustItem(int position) { + View v = getChildAt(position - getFirstVisiblePosition()); + + if (v != null) { + adjustItem(position, v, false); + } + } + + /** + * Sets layout param height, gravity, and visibility on + * wrapped item. + */ + private void adjustItem(int position, View v, boolean invalidChildHeight) { + + // Adjust item height + ViewGroup.LayoutParams lp = v.getLayoutParams(); + int height; + if (position != mSrcPos && position != mFirstExpPos && position != mSecondExpPos) { + height = ViewGroup.LayoutParams.WRAP_CONTENT; + } else { + height = calcItemHeight(position, v, invalidChildHeight); + } + + if (height != lp.height) { + lp.height = height; + v.setLayoutParams(lp); + } + + // Adjust item gravity + if (position == mFirstExpPos || position == mSecondExpPos) { + if (position < mSrcPos) { + ((DragSortItemView) v).setGravity(Gravity.BOTTOM); + } else if (position > mSrcPos) { + ((DragSortItemView) v).setGravity(Gravity.TOP); + } + } + + // Finally adjust item visibility + + int oldVis = v.getVisibility(); + int vis = View.VISIBLE; + + if (position == mSrcPos && mFloatView != null) { + vis = View.INVISIBLE; + } + + if (vis != oldVis) { + v.setVisibility(vis); + } + } + + private int getChildHeight(int position) { + if (position == mSrcPos) { + return 0; + } + + View v = getChildAt(position - getFirstVisiblePosition()); + + if (v != null) { + // item is onscreen, therefore child height is valid, + // hence the "true" + return getChildHeight(position, v, false); + } else { + // item is offscreen + // first check cache for child height at this position + int childHeight = mChildHeightCache.get(position); + if (childHeight != -1) { + // Log.d("mobeta", "found child height in cache!"); + return childHeight; + } + + final ListAdapter adapter = getAdapter(); + int type = adapter.getItemViewType(position); + + // There might be a better place for checking for the following + final int typeCount = adapter.getViewTypeCount(); + if (typeCount != mSampleViewTypes.length) { + mSampleViewTypes = new View[typeCount]; + } + + if (type >= 0) { + if (mSampleViewTypes[type] == null) { + v = adapter.getView(position, null, this); + mSampleViewTypes[type] = v; + } else { + v = adapter.getView(position, mSampleViewTypes[type], this); + } + } else { + // type is HEADER_OR_FOOTER or IGNORE + v = adapter.getView(position, null, this); + } + + // current child height is invalid, hence "true" below + childHeight = getChildHeight(position, v, true); + + // cache it because this could have been expensive + mChildHeightCache.add(position, childHeight); + + return childHeight; + } + } + + private int getChildHeight(int position, View item, boolean invalidChildHeight) { + if (position == mSrcPos) { + return 0; + } + + View child; + if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) { + child = item; + } else { + child = ((ViewGroup) item).getChildAt(0); + } + + ViewGroup.LayoutParams lp = child.getLayoutParams(); + + if (lp != null) { + if (lp.height > 0) { + return lp.height; + } + } + + int childHeight = child.getHeight(); + + if (childHeight == 0 || invalidChildHeight) { + measureItem(child); + childHeight = child.getMeasuredHeight(); + } + + return childHeight; + } + + private int calcItemHeight(int position, View item, boolean invalidChildHeight) { + return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight)); + } + + private int calcItemHeight(int position, int childHeight) { + + int divHeight = getDividerHeight(); + + boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos; + int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed; + int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight); + + int height; + + if (position == mSrcPos) { + if (mSrcPos == mFirstExpPos) { + if (isSliding) { + height = slideHeight + mItemHeightCollapsed; + } else { + height = mFloatViewHeight; + } + } else if (mSrcPos == mSecondExpPos) { + // if gets here, we know an item is sliding + height = mFloatViewHeight - slideHeight; + } else { + height = mItemHeightCollapsed; + } + } else if (position == mFirstExpPos) { + if (isSliding) { + height = childHeight + slideHeight; + } else { + height = childHeight + maxNonSrcBlankHeight; + } + } else if (position == mSecondExpPos) { + // we know an item is sliding (b/c 2ndPos != 1stPos) + height = childHeight + maxNonSrcBlankHeight - slideHeight; + } else { + height = childHeight; + } + + return height; + } + + @Override + public void requestLayout() { + if (!mBlockLayoutRequests) { + super.requestLayout(); + } + } + + private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) { + int adjust = 0; + + final int childHeight = getChildHeight(movePos); + + int moveHeightBefore = moveItem.getHeight(); + int moveHeightAfter = calcItemHeight(movePos, childHeight); + + int moveBlankBefore = moveHeightBefore; + int moveBlankAfter = moveHeightAfter; + if (movePos != mSrcPos) { + moveBlankBefore -= childHeight; + moveBlankAfter -= childHeight; + } + + int maxBlank = mFloatViewHeight; + if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) { + maxBlank -= mItemHeightCollapsed; + } + + if (movePos <= oldFirstExpPos) { + if (movePos > mFirstExpPos) { + adjust += maxBlank - moveBlankAfter; + } + } else if (movePos == oldSecondExpPos) { + if (movePos <= mFirstExpPos) { + adjust += moveBlankBefore - maxBlank; + } else if (movePos == mSecondExpPos) { + adjust += moveHeightBefore - moveHeightAfter; + } else { + adjust += moveBlankBefore; + } + } else { + if (movePos <= mFirstExpPos) { + adjust -= maxBlank; + } else if (movePos == mSecondExpPos) { + adjust -= moveBlankAfter; + } + } + + return adjust; + } + + private void measureItem(View item) { + ViewGroup.LayoutParams lp = item.getLayoutParams(); + if (lp == null) { + lp = new AbsListView.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + item.setLayoutParams(lp); + } + int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft() + + getListPaddingRight(), lp.width); + int hspec; + if (lp.height > 0) { + hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); + } else { + hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + item.measure(wspec, hspec); + } + + private void measureFloatView() { + if (mFloatView != null) { + measureItem(mFloatView); + mFloatViewHeight = mFloatView.getMeasuredHeight(); + mFloatViewHeightHalf = mFloatViewHeight / 2; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // Log.d("mobeta", "onMeasure called"); + if (mFloatView != null) { + if (mFloatView.isLayoutRequested()) { + measureFloatView(); + } + mFloatViewOnMeasured = true; // set to false after layout + } + mWidthMeasureSpec = widthMeasureSpec; + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); + + if (mFloatView != null) { + if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) { + // Have to measure here when usual android measure + // pass is skipped. This happens during a drag-sort + // when layoutChildren is called directly. + measureFloatView(); + } + mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight()); + mFloatViewOnMeasured = false; + } + } + + protected boolean onDragTouchEvent(MotionEvent ev) { + // we are in a drag + int action = ev.getAction() & MotionEvent.ACTION_MASK; + + switch (ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_CANCEL: + if (mDragState == DRAGGING) { + cancelDrag(); + } + doActionUpOrCancel(); + break; + case MotionEvent.ACTION_UP: + // Log.d("mobeta", "calling stopDrag from onDragTouchEvent"); + if (mDragState == DRAGGING) { + stopDrag(false); + } + doActionUpOrCancel(); + break; + case MotionEvent.ACTION_MOVE: + continueDrag((int) ev.getX(), (int) ev.getY()); + break; + } + + return true; + } + + private boolean mFloatViewInvalidated = false; + + private void invalidateFloatView() { + mFloatViewInvalidated = true; + } + + /** + * Start a drag of item at position using the + * registered FloatViewManager. Calls through + * to {@link #startDrag(int,View,int,int,int)} after obtaining + * the floating View from the FloatViewManager. + * + * @param position Item to drag. + * @param dragFlags Flags that restrict some movements of the + * floating View. For example, set dragFlags |= + * ~{@link #DRAG_NEG_X} to allow dragging the floating + * View in all directions except off the screen to the left. + * @param deltaX Offset in x of the touch coordinate from the + * left edge of the floating View (i.e. touch-x minus float View + * left). + * @param deltaY Offset in y of the touch coordinate from the + * top edge of the floating View (i.e. touch-y minus float View + * top). + * + * @return True if the drag was started, false otherwise. This + * startDrag will fail if we are not currently in + * a touch event, there is no registered FloatViewManager, + * or the FloatViewManager returns a null View. + */ + public boolean startDrag(int position, int dragFlags, int deltaX, int deltaY) { + if (!mInTouchEvent || mFloatViewManager == null) { + return false; + } + + View v = mFloatViewManager.onCreateFloatView(position); + + if (v == null) { + return false; + } else { + return startDrag(position, v, dragFlags, deltaX, deltaY); + } + + } + + /** + * Start a drag of item at position without using + * a FloatViewManager. + * + * @param position Item to drag. + * @param floatView Floating View. + * @param dragFlags Flags that restrict some movements of the + * floating View. For example, set dragFlags |= + * ~{@link #DRAG_NEG_X} to allow dragging the floating + * View in all directions except off the screen to the left. + * @param deltaX Offset in x of the touch coordinate from the + * left edge of the floating View (i.e. touch-x minus float View + * left). + * @param deltaY Offset in y of the touch coordinate from the + * top edge of the floating View (i.e. touch-y minus float View + * top). + * + * @return True if the drag was started, false otherwise. This + * startDrag will fail if we are not currently in + * a touch event, floatView is null, or there is + * a drag in progress. + */ + public boolean startDrag(int position, View floatView, int dragFlags, int deltaX, int deltaY) { + if (mDragState != IDLE || !mInTouchEvent || mFloatView != null || floatView == null + || !mDragEnabled) { + return false; + } + + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + + int pos = position + getHeaderViewsCount(); + mFirstExpPos = pos; + mSecondExpPos = pos; + mSrcPos = pos; + mFloatPos = pos; + + // mDragState = dragType; + mDragState = DRAGGING; + mDragFlags = 0; + mDragFlags |= dragFlags; + + mFloatView = floatView; + measureFloatView(); // sets mFloatViewHeight + + mDragDeltaX = deltaX; + mDragDeltaY = deltaY; + mDragStartY = mY; + + // updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY); + mFloatLoc.x = mX - mDragDeltaX; + mFloatLoc.y = mY - mDragDeltaY; + + // set src item invisible + final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition()); + + if (srcItem != null) { + srcItem.setVisibility(View.INVISIBLE); + } + + if (mTrackDragSort) { + mDragSortTracker.startTracking(); + } + + // once float view is created, events are no longer passed + // to ListView + switch (mCancelMethod) { + case ON_TOUCH_EVENT: + super.onTouchEvent(mCancelEvent); + break; + case ON_INTERCEPT_TOUCH_EVENT: + super.onInterceptTouchEvent(mCancelEvent); + break; + } + + requestLayout(); + + if (mLiftAnimator != null) { + mLiftAnimator.start(); + } + + return true; + } + + private void doDragFloatView(boolean forceInvalidate) { + int movePos = getFirstVisiblePosition() + getChildCount() / 2; + View moveItem = getChildAt(getChildCount() / 2); + + if (moveItem == null) { + return; + } + + doDragFloatView(movePos, moveItem, forceInvalidate); + } + + private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) { + mBlockLayoutRequests = true; + + updateFloatView(); + + int oldFirstExpPos = mFirstExpPos; + int oldSecondExpPos = mSecondExpPos; + + boolean updated = updatePositions(); + + if (updated) { + adjustAllItems(); + int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos); + // Log.d("mobeta", " adjust scroll="+scroll); + + setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop()); + layoutChildren(); + } + + if (updated || forceInvalidate) { + invalidate(); + } + + mBlockLayoutRequests = false; + } + + /** + * Sets float View location based on suggested values and + * constraints set in mDragFlags. + */ + private void updateFloatView() { + + if (mFloatViewManager != null) { + mTouchLoc.set(mX, mY); + mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, mTouchLoc); + } + + final int floatX = mFloatLoc.x; + final int floatY = mFloatLoc.y; + + // restrict x motion + int padLeft = getPaddingLeft(); + if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) { + mFloatLoc.x = padLeft; + } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) { + mFloatLoc.x = padLeft; + } + + // keep floating view from going past bottom of last header view + final int numHeaders = getHeaderViewsCount(); + final int numFooters = getFooterViewsCount(); + final int firstPos = getFirstVisiblePosition(); + final int lastPos = getLastVisiblePosition(); + + // Log.d("mobeta", + // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos); + int topLimit = getPaddingTop(); + if (firstPos < numHeaders) { + topLimit = getChildAt(numHeaders - firstPos - 1).getBottom(); + } + if ((mDragFlags & DRAG_NEG_Y) == 0) { + if (firstPos <= mSrcPos) { + topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit); + } + } + // bottom limit is top of first footer View or + // bottom of last item in list + int bottomLimit = getHeight() - getPaddingBottom(); + if (lastPos >= getCount() - numFooters - 1) { + bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom(); + } + if ((mDragFlags & DRAG_POS_Y) == 0) { + if (lastPos >= mSrcPos) { + bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit); + } + } + + // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY)); + // Log.d("mobeta", "limit=" + limit); + // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY); + + if (floatY < topLimit) { + mFloatLoc.y = topLimit; + } else if (floatY + mFloatViewHeight > bottomLimit) { + mFloatLoc.y = bottomLimit - mFloatViewHeight; + } + + // get y-midpoint of floating view (constrained to ListView bounds) + mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf; + } + + private void destroyFloatView() { + if (mFloatView != null) { + mFloatView.setVisibility(GONE); + if (mFloatViewManager != null) { + mFloatViewManager.onDestroyFloatView(mFloatView); + } + mFloatView = null; + invalidate(); + } + } + + /** + * Interface for customization of the floating View appearance + * and dragging behavior. Implement + * your own and pass it to {@link #setFloatViewManager}. If + * your own is not passed, the default {@link SimpleFloatViewManager} + * implementation is used. + */ + public interface FloatViewManager { + /** + * Return the floating View for item at position. + * DragSortListView will measure and layout this View for you, + * so feel free to just inflate it. You can help DSLV by + * setting some {@link ViewGroup.LayoutParams} on this View; + * otherwise it will set some for you (with a width of FILL_PARENT + * and a height of WRAP_CONTENT). + * + * @param position Position of item to drag (NOTE: + * position excludes header Views; thus, if you + * want to call {@link ListView#getChildAt(int)}, you will need + * to add {@link ListView#getHeaderViewsCount()} to the index). + * + * @return The View you wish to display as the floating View. + */ + public View onCreateFloatView(int position); + + /** + * Called whenever the floating View is dragged. Float View + * properties can be changed here. Also, the upcoming location + * of the float View can be altered by setting + * location.x and location.y. + * + * @param floatView The floating View. + * @param location The location (top-left; relative to DSLV + * top-left) at which the float + * View would like to appear, given the current touch location + * and the offset provided in {@link DragSortListView#startDrag}. + * @param touch The current touch location (relative to DSLV + * top-left). + * @param pendingScroll + */ + public void onDragFloatView(View floatView, Point location, Point touch); + + /** + * Called when the float View is dropped; lets you perform + * any necessary cleanup. The internal DSLV floating View + * reference is set to null immediately after this is called. + * + * @param floatView The floating View passed to + * {@link #onCreateFloatView(int)}. + */ + public void onDestroyFloatView(View floatView); + } + + public void setFloatViewManager(FloatViewManager manager) { + mFloatViewManager = manager; + } + + public void setDragListener(DragListener l) { + mDragListener = l; + } + + /** + * Allows for easy toggling between a DragSortListView + * and a regular old ListView. If enabled, items are + * draggable, where the drag init mode determines how + * items are lifted (see {@link setDragInitMode(int)}). + * If disabled, items cannot be dragged. + * + * @param enabled Set true to enable list + * item dragging + */ + public void setDragEnabled(boolean enabled) { + mDragEnabled = enabled; + } + + public boolean isDragEnabled() { + return mDragEnabled; + } + + /** + * This better reorder your ListAdapter! DragSortListView does not do this + * for you; doesn't make sense to. Make sure + * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called + * in your implementation. Furthermore, if you have a choiceMode other than + * none and the ListAdapter does not return true for + * {@link ListAdapter#hasStableIds()}, you will need to call + * {@link #moveCheckState(int, int)} to move the check boxes along with the + * list items. + * + * @param l + */ + public void setDropListener(DropListener l) { + mDropListener = l; + } + + /** + * Probably a no-brainer, but make sure that your remove listener + * calls {@link BaseAdapter#notifyDataSetChanged()} or something like it. + * When an item removal occurs, DragSortListView + * relies on a redraw of all the items to recover invisible views + * and such. Strictly speaking, if you remove something, your dataset + * has changed... + * + * @param l + */ + public void setRemoveListener(RemoveListener l) { + mRemoveListener = l; + } + + public interface DragListener { + public void drag(int from, int to); + } + + /** + * Your implementation of this has to reorder your ListAdapter! + * Make sure to call + * {@link BaseAdapter#notifyDataSetChanged()} or something like it + * in your implementation. + * + * @author heycosmo + * + */ + public interface DropListener { + public void drop(int from, int to); + } + + /** + * Make sure to call + * {@link BaseAdapter#notifyDataSetChanged()} or something like it + * in your implementation. + * + * @author heycosmo + * + */ + public interface RemoveListener { + public void remove(int which); + } + + public interface DragSortListener extends DropListener, DragListener, RemoveListener { + } + + public void setDragSortListener(DragSortListener l) { + setDropListener(l); + setDragListener(l); + setRemoveListener(l); + } + + /** + * Completely custom scroll speed profile. Default increases linearly + * with position and is constant in time. Create your own by implementing + * {@link DragSortListView.DragScrollProfile}. + * + * @param ssp + */ + public void setDragScrollProfile(DragScrollProfile ssp) { + if (ssp != null) { + mScrollProfile = ssp; + } + } + + /** + * Use this to move the check state of an item from one position to another + * in a drop operation. If you have a choiceMode which is not none, this + * method must be called when the order of items changes in an underlying + * adapter which does not have stable IDs (see + * {@link ListAdapter#hasStableIds()}). This is because without IDs, the + * ListView has no way of knowing which items have moved where, and cannot + * update the check state accordingly. + *

+ * A word of warning about a "feature" in Android that you may run into when + * dealing with movable list items: for an adapter that does have + * stable IDs, ListView will attempt to locate each item based on its ID and + * move the check state from the item's old position to the new position — + * which is all fine and good (and removes the need for calling this + * function), except for the half-baked approach. Apparently to save time in + * the naive algorithm used, ListView will only search for an ID in the + * close neighborhood of the old position. If the user moves an item too far + * (specifically, more than 20 rows away), ListView will give up and just + * force the item to be unchecked. So if there is a reasonable chance that + * the user will move items more than 20 rows away from the original + * position, you may wish to use an adapter with unstable IDs and call this + * method manually instead. + * + * @param from + * @param to + */ + public void moveCheckState(int from, int to) { + // This method runs in O(n log n) time (n being the number of list + // items). The bottleneck is the call to AbsListView.setItemChecked, + // which is O(log n) because of the binary search involved in calling + // SparseBooleanArray.put(). + // + // To improve on the average time, we minimize the number of calls to + // setItemChecked by only calling it for items that actually have a + // changed state. This is achieved by building a list containing the + // start and end of the "runs" of checked items, and then moving the + // runs. Note that moving an item from A to B is essentially a rotation + // of the range of items in [A, B]. Let's say we have + // . . U V X Y Z . . + // and move U after Z. This is equivalent to a rotation one step to the + // left within the range you are moving across: + // . . V X Y Z U . . + // + // So, to perform the move we enumerate all the runs within the move + // range, then rotate each run one step to the left or right (depending + // on move direction). For example, in the list: + // X X . X X X . X + // we have two runs. One begins at the last item of the list and wraps + // around to the beginning, ending at position 1. The second begins at + // position 3 and ends at position 5. To rotate a run, regardless of + // length, we only need to set a check mark at one end of the run, and + // clear a check mark at the other end: + // X . X X X . X X + SparseBooleanArray cip = getCheckedItemPositions(); + int rangeStart = from; + int rangeEnd = to; + if (to < from) { + rangeStart = to; + rangeEnd = from; + } + rangeEnd += 1; + + int[] runStart = new int[cip.size()]; + int[] runEnd = new int[cip.size()]; + int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd); + if (runCount == 1 && (runStart[0] == runEnd[0])) { + // Special case where all items are checked, we can never set any + // item to false like we do below. + return; + } + + if (from < to) { + for (int i = 0; i != runCount; i++) { + setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true); + setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false); + } + + } else { + for (int i = 0; i != runCount; i++) { + setItemChecked(runStart[i], false); + setItemChecked(runEnd[i], true); + } + } + } + + /** + * Use this when an item has been deleted, to move the check state of all + * following items up one step. If you have a choiceMode which is not none, + * this method must be called when the order of items changes in an + * underlying adapter which does not have stable IDs (see + * {@link ListAdapter#hasStableIds()}). This is because without IDs, the + * ListView has no way of knowing which items have moved where, and cannot + * update the check state accordingly. + * + * See also further comments on {@link #moveCheckState(int, int)}. + * + * @param position + */ + public void removeCheckState(int position) { + SparseBooleanArray cip = getCheckedItemPositions(); + + if (cip.size() == 0) + return; + int[] runStart = new int[cip.size()]; + int[] runEnd = new int[cip.size()]; + int rangeStart = position; + int rangeEnd = cip.keyAt(cip.size() - 1) + 1; + int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd); + for (int i = 0; i != runCount; i++) { + if (!(runStart[i] == position || (runEnd[i] < runStart[i] && runEnd[i] > position))) { + // Only set a new check mark in front of this run if it does + // not contain the deleted position. If it does, we only need + // to make it one check mark shorter at the end. + setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true); + } + setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false); + } + } + + private static int buildRunList(SparseBooleanArray cip, int rangeStart, + int rangeEnd, int[] runStart, int[] runEnd) { + int runCount = 0; + + int i = findFirstSetIndex(cip, rangeStart, rangeEnd); + if (i == -1) + return 0; + + int position = cip.keyAt(i); + int currentRunStart = position; + int currentRunEnd = currentRunStart + 1; + for (i++; i < cip.size() && (position = cip.keyAt(i)) < rangeEnd; i++) { + if (!cip.valueAt(i)) // not checked => not interesting + continue; + if (position == currentRunEnd) { + currentRunEnd++; + } else { + runStart[runCount] = currentRunStart; + runEnd[runCount] = currentRunEnd; + runCount++; + currentRunStart = position; + currentRunEnd = position + 1; + } + } + + if (currentRunEnd == rangeEnd) { + // rangeStart and rangeEnd are equivalent positions so to be + // consistent we translate them to the same integer value. That way + // we can check whether a run covers the entire range by just + // checking if the start equals the end position. + currentRunEnd = rangeStart; + } + runStart[runCount] = currentRunStart; + runEnd[runCount] = currentRunEnd; + runCount++; + + if (runCount > 1) { + if (runStart[0] == rangeStart && runEnd[runCount - 1] == rangeStart) { + // The last run ends at the end of the range, and the first run + // starts at the beginning of the range. So they are actually + // part of the same run, except they wrap around the end of the + // range. To avoid adjacent runs, we need to merge them. + runStart[0] = runStart[runCount - 1]; + runCount--; + } + } + return runCount; + } + + private static int rotate(int value, int offset, int lowerBound, int upperBound) { + int windowSize = upperBound - lowerBound; + + value += offset; + if (value < lowerBound) { + value += windowSize; + } else if (value >= upperBound) { + value -= windowSize; + } + return value; + } + + private static int findFirstSetIndex(SparseBooleanArray sba, int rangeStart, int rangeEnd) { + int size = sba.size(); + int i = insertionIndexForKey(sba, rangeStart); + while (i < size && sba.keyAt(i) < rangeEnd && !sba.valueAt(i)) + i++; + if (i == size || sba.keyAt(i) >= rangeEnd) + return -1; + return i; + } + + private static int insertionIndexForKey(SparseBooleanArray sba, int key) { + int low = 0; + int high = sba.size(); + while (high - low > 0) { + int middle = (low + high) >> 1; + if (sba.keyAt(middle) < key) + low = middle + 1; + else + high = middle; + } + return low; + } + + /** + * Interface for controlling + * scroll speed as a function of touch position and time. Use + * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to + * set custom profile. + * + * @author heycosmo + * + */ + public interface DragScrollProfile { + /** + * Return a scroll speed in pixels/millisecond. Always return a + * positive number. + * + * @param w Normalized position in scroll region (i.e. w \in [0,1]). + * Small w typically means slow scrolling. + * @param t Time (in milliseconds) since start of scroll (handy if you + * want scroll acceleration). + * @return Scroll speed at position w and time t in pixels/ms. + */ + float getSpeed(float w, long t); + } + + private class DragScroller implements Runnable { + + private boolean mAbort; + + private long mPrevTime; + private long mCurrTime; + + private int dy; + private float dt; + private long tStart; + private int scrollDir; + + public final static int STOP = -1; + public final static int UP = 0; + public final static int DOWN = 1; + + private float mScrollSpeed; // pixels per ms + + private boolean mScrolling = false; + + private int mLastHeader; + private int mFirstFooter; + + public boolean isScrolling() { + return mScrolling; + } + + public int getScrollDir() { + return mScrolling ? scrollDir : STOP; + } + + public DragScroller() { + } + + public void startScrolling(int dir) { + if (!mScrolling) { + // Debug.startMethodTracing("dslv-scroll"); + mAbort = false; + mScrolling = true; + tStart = SystemClock.uptimeMillis(); + mPrevTime = tStart; + scrollDir = dir; + post(this); + } + } + + public void stopScrolling(boolean now) { + if (now) { + DragSortListView.this.removeCallbacks(this); + mScrolling = false; + } else { + mAbort = true; + } + + // Debug.stopMethodTracing(); + } + + @Override + public void run() { + if (mAbort) { + mScrolling = false; + return; + } + + // Log.d("mobeta", "scroll"); + + final int first = getFirstVisiblePosition(); + final int last = getLastVisiblePosition(); + final int count = getCount(); + final int padTop = getPaddingTop(); + final int listHeight = getHeight() - padTop - getPaddingBottom(); + + int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf); + int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf); + + if (scrollDir == UP) { + View v = getChildAt(0); + // Log.d("mobeta", "vtop="+v.getTop()+" padtop="+padTop); + if (v == null) { + mScrolling = false; + return; + } else { + if (first == 0 && v.getTop() == padTop) { + mScrolling = false; + return; + } + } + mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY) + / mDragUpScrollHeight, mPrevTime); + } else { + View v = getChildAt(last - first); + if (v == null) { + mScrolling = false; + return; + } else { + if (last == count - 1 && v.getBottom() <= listHeight + padTop) { + mScrolling = false; + return; + } + } + mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF) + / mDragDownScrollHeight, mPrevTime); + } + + mCurrTime = SystemClock.uptimeMillis(); + dt = (float) (mCurrTime - mPrevTime); + + // dy is change in View position of a list item; i.e. positive dy + // means user is scrolling up (list item moves down the screen, + // remember + // y=0 is at top of View). + dy = (int) Math.round(mScrollSpeed * dt); + + int movePos; + if (dy >= 0) { + dy = Math.min(listHeight, dy); + movePos = first; + } else { + dy = Math.max(-listHeight, dy); + movePos = last; + } + + final View moveItem = getChildAt(movePos - first); + int top = moveItem.getTop() + dy; + + if (movePos == 0 && top > padTop) { + top = padTop; + } + + // always do scroll + mBlockLayoutRequests = true; + + setSelectionFromTop(movePos, top - padTop); + DragSortListView.this.layoutChildren(); + invalidate(); + + mBlockLayoutRequests = false; + + // scroll means relative float View movement + doDragFloatView(movePos, moveItem, false); + + mPrevTime = mCurrTime; + // Log.d("mobeta", " updated prevTime="+mPrevTime); + + post(this); + } + } + + private class DragSortTracker { + StringBuilder mBuilder = new StringBuilder(); + + File mFile; + + private int mNumInBuffer = 0; + private int mNumFlushes = 0; + + private boolean mTracking = false; + + public DragSortTracker() { + File root = Environment.getExternalStorageDirectory(); + mFile = new File(root, "dslv_state.txt"); + + if (!mFile.exists()) { + try { + mFile.createNewFile(); + Log.d("mobeta", "file created"); + } catch (IOException e) { + Log.w("mobeta", "Could not create dslv_state.txt"); + Log.d("mobeta", e.getMessage()); + } + } + + } + + public void startTracking() { + mBuilder.append("\n"); + mNumFlushes = 0; + mTracking = true; + } + + public void appendState() { + if (!mTracking) { + return; + } + + mBuilder.append("\n"); + final int children = getChildCount(); + final int first = getFirstVisiblePosition(); + mBuilder.append(" "); + for (int i = 0; i < children; ++i) { + mBuilder.append(first + i).append(","); + } + mBuilder.append("\n"); + + mBuilder.append(" "); + for (int i = 0; i < children; ++i) { + mBuilder.append(getChildAt(i).getTop()).append(","); + } + mBuilder.append("\n"); + mBuilder.append(" "); + for (int i = 0; i < children; ++i) { + mBuilder.append(getChildAt(i).getBottom()).append(","); + } + mBuilder.append("\n"); + + mBuilder.append(" ").append(mFirstExpPos).append("\n"); + mBuilder.append(" ") + .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos)) + .append("\n"); + mBuilder.append(" ").append(mSecondExpPos).append("\n"); + mBuilder.append(" ") + .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos)) + .append("\n"); + mBuilder.append(" ").append(mSrcPos).append("\n"); + mBuilder.append(" ").append(mFloatViewHeight + getDividerHeight()) + .append("\n"); + mBuilder.append(" ").append(getHeight()).append("\n"); + mBuilder.append(" ").append(mLastY).append("\n"); + mBuilder.append(" ").append(mFloatViewMid).append("\n"); + mBuilder.append(" "); + for (int i = 0; i < children; ++i) { + mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(","); + } + mBuilder.append("\n"); + + mBuilder.append("\n"); + mNumInBuffer++; + + if (mNumInBuffer > 1000) { + flush(); + mNumInBuffer = 0; + } + } + + public void flush() { + if (!mTracking) { + return; + } + + // save to file on sdcard + try { + boolean append = true; + if (mNumFlushes == 0) { + append = false; + } + FileWriter writer = new FileWriter(mFile, append); + + writer.write(mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + + writer.flush(); + writer.close(); + + mNumFlushes++; + } catch (IOException e) { + // do nothing + } + } + + public void stopTracking() { + if (mTracking) { + mBuilder.append("\n"); + flush(); + mTracking = false; + } + } + + } + +} diff --git a/src/com/liquid/liquidlounge/preferences/dslv/ShortcutPickerHelper.java b/src/com/liquid/liquidlounge/preferences/dslv/ShortcutPickerHelper.java new file mode 100644 index 00000000..2bc0026f --- /dev/null +++ b/src/com/liquid/liquidlounge/preferences/dslv/ShortcutPickerHelper.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2014 SlimRoms 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.liquid.liquidlounge.preferences.dslv; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.TextView; + +import com.liquid.liquidlounge.preferences.dslv.ShortcutPickerHelper.AppExpandableAdapter.GroupInfo; +import com.android.settings.R; + +import com.android.internal.util.slim.AppHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class ShortcutPickerHelper { + + public static final int REQUEST_PICK_SHORTCUT = 100; + public static final int REQUEST_PICK_APPLICATION = 101; + public static final int REQUEST_CREATE_SHORTCUT = 102; + + private Activity mParent; + private AlertDialog mAlertDialog; + private OnPickListener mListener; + private PackageManager mPackageManager; + private int lastFragmentId; + + public interface OnPickListener { + void shortcutPicked(String uri, String friendlyName, Bitmap bmp, boolean isApplication); + } + + public ShortcutPickerHelper(Activity parent, OnPickListener listener) { + mParent = parent; + mPackageManager = mParent.getPackageManager(); + mListener = listener; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case REQUEST_PICK_APPLICATION: + completeSetCustomApp(data); + break; + case REQUEST_CREATE_SHORTCUT: + completeSetCustomShortcut(data); + break; + case REQUEST_PICK_SHORTCUT: + processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT); + break; + } + } + } + + public void pickShortcut(int fragmentId) { + pickShortcut(fragmentId, false); + } + + public void pickShortcut(int fragmentId, boolean fullAppsOnly) { + pickShortcut(fragmentId, fullAppsOnly, false, false); + } + + + public void pickShortcut(int fragmentId, boolean fullAppsOnly, + boolean newAction, boolean reassignAction) { + lastFragmentId = fragmentId; + + if (fullAppsOnly) { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); + startFragmentOrActivity(pickIntent, REQUEST_PICK_APPLICATION); + } else { + Bundle bundle = new Bundle(); + + ArrayList shortcutNames = new ArrayList(); + shortcutNames.add(mParent.getString(R.string.shortcut_picker_applications_title)); + shortcutNames.add(mParent.getString(R.string.shortcut_picker_activities_title)); + bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); + + ArrayList shortcutIcons = new ArrayList(); + shortcutIcons.add(ShortcutIconResource.fromContext(mParent, + android.R.drawable.sym_def_app_icon)); + shortcutIcons.add(ShortcutIconResource.fromContext(mParent, R.drawable.activities_icon)); + bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); + + CharSequence title; + if (newAction) { + title = mParent.getText( + R.string.shortcut_picker_choose_new_action); + } else if (reassignAction) { + title = mParent.getText( + R.string.shortcut_picker_reassign_action); + } else { + title = mParent.getText( + R.string.shortcut_picker_choose_action); + } + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT)); + pickIntent.putExtra(Intent.EXTRA_TITLE, title); + pickIntent.putExtras(bundle); + startFragmentOrActivity(pickIntent, REQUEST_PICK_SHORTCUT); + } + } + + private void startFragmentOrActivity(Intent pickIntent, int requestCode) { + if (lastFragmentId == 0) { + mParent.startActivityForResult(pickIntent, requestCode); + } else { + Fragment cFrag = mParent.getFragmentManager().findFragmentById(lastFragmentId); + if (cFrag != null) { + mParent.startActivityFromFragment(cFrag, pickIntent, requestCode); + } + } + } + + private void processShortcut(Intent intent, + int requestCodeApplication, int requestCodeShortcut) { + // Handle case where user selected "Applications" + String applicationName = mParent.getResources().getString(R.string.shortcut_picker_applications_title); + String application2name = mParent.getString(R.string.shortcut_picker_activities_title); + String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + + if (applicationName != null && applicationName.equals(shortcutName)) { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); + pickIntent.putExtra(Intent.EXTRA_TITLE, mParent.getText( + R.string.shortcut_picker_select_app_title)); + startFragmentOrActivity(pickIntent, requestCodeApplication); + } else if (application2name != null && application2name.equals(shortcutName)){ + final List pInfos = mPackageManager.getInstalledPackages(PackageManager.GET_ACTIVITIES); + ExpandableListView appListView = new ExpandableListView(mParent); + AppExpandableAdapter appAdapter = new AppExpandableAdapter(pInfos, mParent); + appListView.setAdapter(appAdapter); + appListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { + @Override + public boolean onChildClick(ExpandableListView parent, View v, + int groupPosition, int childPosition, long id) { + Intent shortIntent = new Intent(Intent.ACTION_MAIN); + String pkgName = ((GroupInfo)parent.getExpandableListAdapter().getGroup(groupPosition)) + .info.packageName; + String actName = ((GroupInfo)parent.getExpandableListAdapter().getGroup(groupPosition)) + .info.activities[childPosition].name; + shortIntent.setClassName(pkgName, actName); + completeSetCustomApp(shortIntent); + mAlertDialog.dismiss(); + return true; + } + }); + Builder builder = new Builder(mParent, com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert); + builder.setView(appListView); + mAlertDialog = builder.create(); + mAlertDialog.setTitle(mParent.getString(R.string.shortcut_picker_select_activity_title)); + mAlertDialog.show(); + mAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + + } + }); + } else { + startFragmentOrActivity(intent, requestCodeShortcut); + } + } + + public class AppExpandableAdapter extends BaseExpandableListAdapter { + + ArrayList allList = new ArrayList(); + final int groupPadding; + + public class LabelCompare implements Comparator{ + @Override + public int compare(GroupInfo item1, GroupInfo item2) { + String rank1 = item1.label.toLowerCase(); + String rank2 = item2.label.toLowerCase(); + int result = rank1.compareTo(rank2); + if(result == 0) { + return 0; + } else if(result < 0) { + return -1; + } else { + return +1; + } + } + } + + class GroupInfo { + String label; + PackageInfo info; + GroupInfo (String l, PackageInfo p) { + label = l; + info = p; + } + } + + public AppExpandableAdapter(List pInfos, Context context) { + for (PackageInfo i : pInfos) { + allList.add(new GroupInfo(i.applicationInfo.loadLabel(mPackageManager).toString(), i)); + } + Collections.sort(allList, new LabelCompare()); + groupPadding = context.getResources().getDimensionPixelSize(R.dimen.shortcut_picker_left_padding); + } + + public String getChild(int groupPosition, int childPosition) { + return allList.get(groupPosition).info.activities[childPosition].name; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public int getChildrenCount(int groupPosition) { + if (allList.get(groupPosition).info.activities != null) { + return allList.get(groupPosition).info.activities.length; + } else { + return 0; + } + } + + + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = View.inflate(mParent, android.R.layout.simple_list_item_1, null); + convertView.setPadding(groupPadding, 0, 0, 0); + + } + TextView textView = (TextView)convertView.findViewById(android.R.id.text1); + textView.setText(getChild(groupPosition, childPosition).replaceFirst(allList.get(groupPosition).info.packageName + ".", "")); + return convertView; + } + + public GroupInfo getGroup(int groupPosition) { + return allList.get(groupPosition); + } + + public int getGroupCount() { + return allList.size(); + } + + public long getGroupId(int groupPosition) { + return groupPosition; + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + if (convertView == null) { + convertView = View.inflate(mParent, android.R.layout.simple_list_item_1, null); + convertView.setPadding(groupPadding, 0, 0, 0); + } + TextView textView = (TextView)convertView.findViewById(android.R.id.text1); + textView.setText(getGroup(groupPosition).label); + return convertView; + } + + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public boolean hasStableIds() { + return true; + } + + } + + private void completeSetCustomApp(Intent data) { + mListener.shortcutPicked(data.toUri(0), + AppHelper.getFriendlyActivityName(mParent, mPackageManager, data, false), null, true); + } + + private void completeSetCustomShortcut(Intent data) { + Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + /* preserve shortcut name, we want to restore it later */ + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, data.getStringExtra( + Intent.EXTRA_SHORTCUT_NAME)); + String appUri = intent.toUri(0); + appUri = appUri.replaceAll("com.android.contacts.action.QUICK_CONTACT", + "android.intent.action.VIEW"); + + // Check if icon is present + Bitmap bmp = null; + Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); + if (extra != null && extra instanceof Bitmap) { + bmp = (Bitmap) extra; + } + // No icon till now check if icon resource is present + if (bmp == null) { + extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); + if (extra != null && extra instanceof Intent.ShortcutIconResource) { + try { + Intent.ShortcutIconResource iconResource = (ShortcutIconResource) extra; + Resources resources = + mPackageManager.getResourcesForApplication(iconResource.packageName); + final int id = resources.getIdentifier(iconResource.resourceName, null, null); + bmp = BitmapFactory.decodeResource(resources, id); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + mListener.shortcutPicked(appUri, + AppHelper.getFriendlyShortcutName(mParent, mPackageManager, intent), bmp, false); + } +} diff --git a/src/com/liquid/liquidlounge/preferences/dslv/SimpleFloatViewManager.java b/src/com/liquid/liquidlounge/preferences/dslv/SimpleFloatViewManager.java new file mode 100644 index 00000000..3d88a77a --- /dev/null +++ b/src/com/liquid/liquidlounge/preferences/dslv/SimpleFloatViewManager.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012 Carl Bauer + * Copyright (C) 2014 SlimRoms 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.liquid.liquidlounge.preferences.dslv; + +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Color; +import android.widget.ListView; +import android.widget.ImageView; +import android.view.View; +import android.view.ViewGroup; +import android.util.Log; + +/** + * Simple implementation of the FloatViewManager class. Uses list + * items as they appear in the ListView to create the floating View. + */ +public class SimpleFloatViewManager implements DragSortListView.FloatViewManager { + + private Bitmap mFloatBitmap; + + private ImageView mImageView; + + private int mFloatBGColor = Color.BLACK; + + private ListView mListView; + + public SimpleFloatViewManager(ListView lv) { + mListView = lv; + } + + public void setBackgroundColor(int color) { + mFloatBGColor = color; + } + + /** + * This simple implementation creates a Bitmap copy of the + * list item currently shown at ListView position. + */ + @Override + public View onCreateFloatView(int position) { + // Guaranteed that this will not be null? I think so. Nope, got + // a NullPointerException once... + View v = mListView.getChildAt( + position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition()); + + if (v == null) { + return null; + } + + v.setPressed(false); + + // Create a copy of the drawing cache so that it does not get + // recycled by the framework when the list tries to clean up memory + //v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); + v.setDrawingCacheEnabled(true); + mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache()); + v.setDrawingCacheEnabled(false); + + if (mImageView == null) { + mImageView = new ImageView(mListView.getContext()); + } + mImageView.setBackgroundColor(mFloatBGColor); + mImageView.setPadding(0, 0, 0, 0); + mImageView.setImageBitmap(mFloatBitmap); + mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight())); + + return mImageView; + } + + /** + * This does nothing + */ + @Override + public void onDragFloatView(View floatView, Point position, Point touch) { + // do nothing + } + + /** + * Removes the Bitmap from the ImageView created in + * onCreateFloatView() and tells the system to recycle it. + */ + @Override + public void onDestroyFloatView(View floatView) { + ((ImageView) floatView).setImageDrawable(null); + + mFloatBitmap.recycle(); + mFloatBitmap = null; + } + +}