From 9b32cf87e2aaa01926cddbb1700b41eed4576dfb Mon Sep 17 00:00:00 2001 From: Dominik Date: Sun, 11 Mar 2012 17:33:40 +0100 Subject: [PATCH] Started using ActionBarSherlock --- com_actionbarsherlock/.gitignore | 23 + com_actionbarsherlock/AndroidManifest.xml | 13 + com_actionbarsherlock/README.md | 15 + com_actionbarsherlock/build.xml | 85 + .../libs/android-support-v4.jar | Bin 0 -> 247894 bytes com_actionbarsherlock/pom.xml | 151 ++ com_actionbarsherlock/project.properties | 12 + ...s__primary_text_disable_only_holo_dark.xml | 20 + ...__primary_text_disable_only_holo_light.xml | 21 + .../res/color/abs__primary_text_holo_dark.xml | 24 + .../color/abs__primary_text_holo_light.xml | 26 + .../abs__ab_bottom_solid_dark_holo.9.png | Bin 0 -> 144 bytes .../abs__ab_bottom_solid_inverse_holo.9.png | Bin 0 -> 138 bytes .../abs__ab_bottom_solid_light_holo.9.png | Bin 0 -> 144 bytes ...abs__ab_bottom_transparent_dark_holo.9.png | Bin 0 -> 135 bytes ...bs__ab_bottom_transparent_light_holo.9.png | Bin 0 -> 134 bytes .../abs__ab_solid_dark_holo.9.png | Bin 0 -> 146 bytes .../abs__ab_solid_light_holo.9.png | Bin 0 -> 145 bytes .../abs__ab_solid_shadow_holo.9.png | Bin 0 -> 192 bytes .../abs__ab_stacked_solid_dark_holo.9.png | Bin 0 -> 146 bytes .../abs__ab_stacked_solid_inverse_holo.9.png | Bin 0 -> 146 bytes .../abs__ab_stacked_solid_light_holo.9.png | Bin 0 -> 146 bytes ...bs__ab_stacked_transparent_dark_holo.9.png | Bin 0 -> 139 bytes ...s__ab_stacked_transparent_light_holo.9.png | Bin 0 -> 133 bytes .../abs__ab_transparent_dark_holo.9.png | Bin 0 -> 155 bytes .../abs__ab_transparent_light_holo.9.png | Bin 0 -> 145 bytes .../abs__btn_cab_done_default_holo_dark.9.png | Bin 0 -> 104 bytes ...abs__btn_cab_done_default_holo_light.9.png | Bin 0 -> 102 bytes .../abs__btn_cab_done_focused_holo_dark.9.png | Bin 0 -> 112 bytes ...abs__btn_cab_done_focused_holo_light.9.png | Bin 0 -> 108 bytes .../abs__btn_cab_done_pressed_holo_dark.9.png | Bin 0 -> 110 bytes ...abs__btn_cab_done_pressed_holo_light.9.png | Bin 0 -> 108 bytes ...abs__cab_background_bottom_holo_dark.9.png | Bin 0 -> 149 bytes ...bs__cab_background_bottom_holo_light.9.png | Bin 0 -> 145 bytes .../abs__cab_background_top_holo_dark.9.png | Bin 0 -> 147 bytes .../abs__cab_background_top_holo_light.9.png | Bin 0 -> 147 bytes .../abs__dialog_full_holo_dark.9.png | Bin 0 -> 1414 bytes .../abs__dialog_full_holo_light.9.png | Bin 0 -> 1537 bytes .../abs__ic_ab_back_holo_dark.png | Bin 0 -> 602 bytes .../abs__ic_ab_back_holo_light.png | Bin 0 -> 546 bytes .../abs__ic_cab_done_holo_dark.png | Bin 0 -> 713 bytes .../abs__ic_cab_done_holo_light.png | Bin 0 -> 737 bytes ..._ic_menu_moreoverflow_normal_holo_dark.png | Bin 0 -> 144 bytes ...ic_menu_moreoverflow_normal_holo_light.png | Bin 0 -> 148 bytes .../abs__list_divider_holo_dark.9.png | Bin 0 -> 78 bytes .../abs__list_divider_holo_light.9.png | Bin 0 -> 76 bytes .../abs__list_focused_holo.9.png | Bin 0 -> 159 bytes .../abs__list_longpressed_holo.9.png | Bin 0 -> 154 bytes .../abs__list_pressed_holo_dark.9.png | Bin 0 -> 159 bytes .../abs__list_pressed_holo_light.9.png | Bin 0 -> 159 bytes ...bs__list_selector_disabled_holo_dark.9.png | Bin 0 -> 189 bytes ...s__list_selector_disabled_holo_light.9.png | Bin 0 -> 189 bytes .../abs__menu_dropdown_panel_holo_dark.9.png | Bin 0 -> 922 bytes .../abs__menu_dropdown_panel_holo_light.9.png | Bin 0 -> 1061 bytes .../abs__progress_bg_holo_dark.9.png | Bin 0 -> 178 bytes .../abs__progress_bg_holo_light.9.png | Bin 0 -> 174 bytes .../abs__progress_primary_holo_dark.9.png | Bin 0 -> 917 bytes .../abs__progress_primary_holo_light.9.png | Bin 0 -> 917 bytes .../abs__progress_secondary_holo_dark.9.png | Bin 0 -> 188 bytes .../abs__progress_secondary_holo_light.9.png | Bin 0 -> 188 bytes .../abs__spinner_48_inner_holo.png | Bin 0 -> 2081 bytes .../abs__spinner_48_outer_holo.png | Bin 0 -> 1811 bytes .../abs__spinner_ab_default_holo_dark.9.png | Bin 0 -> 311 bytes .../abs__spinner_ab_default_holo_light.9.png | Bin 0 -> 312 bytes .../abs__spinner_ab_disabled_holo_dark.9.png | Bin 0 -> 306 bytes .../abs__spinner_ab_disabled_holo_light.9.png | Bin 0 -> 306 bytes .../abs__spinner_ab_focused_holo_dark.9.png | Bin 0 -> 524 bytes .../abs__spinner_ab_focused_holo_light.9.png | Bin 0 -> 523 bytes .../abs__spinner_ab_pressed_holo_dark.9.png | Bin 0 -> 464 bytes .../abs__spinner_ab_pressed_holo_light.9.png | Bin 0 -> 458 bytes .../abs__tab_selected_focused_holo.9.png | Bin 0 -> 147 bytes .../abs__tab_selected_holo.9.png | Bin 0 -> 148 bytes .../abs__tab_selected_pressed_holo.9.png | Bin 0 -> 147 bytes .../abs__tab_unselected_focused_holo.9.png | Bin 0 -> 146 bytes .../abs__tab_unselected_holo.9.png | Bin 0 -> 153 bytes .../abs__tab_unselected_pressed_holo.9.png | Bin 0 -> 145 bytes .../abs__ab_bottom_solid_dark_holo.9.png | Bin 0 -> 134 bytes .../abs__ab_bottom_solid_inverse_holo.9.png | Bin 0 -> 129 bytes .../abs__ab_bottom_solid_light_holo.9.png | Bin 0 -> 134 bytes ...abs__ab_bottom_transparent_dark_holo.9.png | Bin 0 -> 123 bytes ...bs__ab_bottom_transparent_light_holo.9.png | Bin 0 -> 123 bytes .../abs__ab_solid_dark_holo.9.png | Bin 0 -> 133 bytes .../abs__ab_solid_light_holo.9.png | Bin 0 -> 133 bytes .../abs__ab_solid_shadow_holo.9.png | Bin 0 -> 168 bytes .../abs__ab_stacked_solid_dark_holo.9.png | Bin 0 -> 134 bytes .../abs__ab_stacked_solid_inverse_holo.9.png | Bin 0 -> 133 bytes .../abs__ab_stacked_solid_light_holo.9.png | Bin 0 -> 133 bytes ...bs__ab_stacked_transparent_dark_holo.9.png | Bin 0 -> 127 bytes ...s__ab_stacked_transparent_light_holo.9.png | Bin 0 -> 123 bytes .../abs__ab_transparent_dark_holo.9.png | Bin 0 -> 139 bytes .../abs__ab_transparent_light_holo.9.png | Bin 0 -> 133 bytes .../abs__btn_cab_done_default_holo_dark.9.png | Bin 0 -> 101 bytes ...abs__btn_cab_done_default_holo_light.9.png | Bin 0 -> 99 bytes .../abs__btn_cab_done_focused_holo_dark.9.png | Bin 0 -> 109 bytes ...abs__btn_cab_done_focused_holo_light.9.png | Bin 0 -> 105 bytes .../abs__btn_cab_done_pressed_holo_dark.9.png | Bin 0 -> 107 bytes ...abs__btn_cab_done_pressed_holo_light.9.png | Bin 0 -> 105 bytes ...abs__cab_background_bottom_holo_dark.9.png | Bin 0 -> 127 bytes ...bs__cab_background_bottom_holo_light.9.png | Bin 0 -> 124 bytes .../abs__cab_background_top_holo_dark.9.png | Bin 0 -> 130 bytes .../abs__cab_background_top_holo_light.9.png | Bin 0 -> 128 bytes .../abs__dialog_bottom_holo_dark.9.png | Bin 0 -> 582 bytes .../abs__dialog_bottom_holo_light.9.png | Bin 0 -> 622 bytes .../abs__ic_ab_back_holo_dark.png | Bin 0 -> 466 bytes .../abs__ic_ab_back_holo_light.png | Bin 0 -> 438 bytes .../abs__ic_cab_done_holo_dark.png | Bin 0 -> 566 bytes .../abs__ic_cab_done_holo_light.png | Bin 0 -> 552 bytes ..._ic_menu_moreoverflow_normal_holo_dark.png | Bin 0 -> 122 bytes ...ic_menu_moreoverflow_normal_holo_light.png | Bin 0 -> 131 bytes .../abs__list_divider_holo_dark.9.png | Bin 0 -> 78 bytes .../abs__list_divider_holo_light.9.png | Bin 0 -> 76 bytes .../abs__list_focused_holo.9.png | Bin 0 -> 158 bytes .../abs__list_longpressed_holo.9.png | Bin 0 -> 151 bytes .../abs__list_pressed_holo_dark.9.png | Bin 0 -> 158 bytes .../abs__list_pressed_holo_light.9.png | Bin 0 -> 158 bytes ...bs__list_selector_disabled_holo_dark.9.png | Bin 0 -> 172 bytes ...s__list_selector_disabled_holo_light.9.png | Bin 0 -> 171 bytes .../abs__menu_dropdown_panel_holo_dark.9.png | Bin 0 -> 651 bytes .../abs__menu_dropdown_panel_holo_light.9.png | Bin 0 -> 720 bytes .../abs__progress_bg_holo_dark.9.png | Bin 0 -> 165 bytes .../abs__progress_bg_holo_light.9.png | Bin 0 -> 159 bytes .../abs__progress_primary_holo_dark.9.png | Bin 0 -> 572 bytes .../abs__progress_primary_holo_light.9.png | Bin 0 -> 572 bytes .../abs__progress_secondary_holo_dark.9.png | Bin 0 -> 170 bytes .../abs__progress_secondary_holo_light.9.png | Bin 0 -> 170 bytes .../abs__spinner_48_inner_holo.png | Bin 0 -> 1336 bytes .../abs__spinner_48_outer_holo.png | Bin 0 -> 1165 bytes .../abs__spinner_ab_default_holo_dark.9.png | Bin 0 -> 254 bytes .../abs__spinner_ab_default_holo_light.9.png | Bin 0 -> 255 bytes .../abs__spinner_ab_disabled_holo_dark.9.png | Bin 0 -> 249 bytes .../abs__spinner_ab_disabled_holo_light.9.png | Bin 0 -> 249 bytes .../abs__spinner_ab_focused_holo_dark.9.png | Bin 0 -> 417 bytes .../abs__spinner_ab_focused_holo_light.9.png | Bin 0 -> 424 bytes .../abs__spinner_ab_pressed_holo_dark.9.png | Bin 0 -> 370 bytes .../abs__spinner_ab_pressed_holo_light.9.png | Bin 0 -> 370 bytes .../abs__tab_selected_focused_holo.9.png | Bin 0 -> 148 bytes .../abs__tab_selected_holo.9.png | Bin 0 -> 151 bytes .../abs__tab_selected_pressed_holo.9.png | Bin 0 -> 150 bytes .../abs__tab_unselected_focused_holo.9.png | Bin 0 -> 150 bytes .../abs__tab_unselected_holo.9.png | Bin 0 -> 157 bytes .../abs__tab_unselected_pressed_holo.9.png | Bin 0 -> 155 bytes .../abs__progress_medium_holo.xml | 34 + .../abs__ab_bottom_solid_dark_holo.9.png | Bin 0 -> 165 bytes .../abs__ab_bottom_solid_inverse_holo.9.png | Bin 0 -> 157 bytes .../abs__ab_bottom_solid_light_holo.9.png | Bin 0 -> 166 bytes ...abs__ab_bottom_transparent_dark_holo.9.png | Bin 0 -> 153 bytes ...bs__ab_bottom_transparent_light_holo.9.png | Bin 0 -> 152 bytes .../abs__ab_solid_dark_holo.9.png | Bin 0 -> 163 bytes .../abs__ab_solid_light_holo.9.png | Bin 0 -> 163 bytes .../abs__ab_solid_shadow_holo.9.png | Bin 0 -> 290 bytes .../abs__ab_stacked_solid_dark_holo.9.png | Bin 0 -> 163 bytes .../abs__ab_stacked_solid_inverse_holo.9.png | Bin 0 -> 163 bytes .../abs__ab_stacked_solid_light_holo.9.png | Bin 0 -> 163 bytes ...bs__ab_stacked_transparent_dark_holo.9.png | Bin 0 -> 158 bytes ...s__ab_stacked_transparent_light_holo.9.png | Bin 0 -> 152 bytes .../abs__ab_transparent_dark_holo.9.png | Bin 0 -> 171 bytes .../abs__ab_transparent_light_holo.9.png | Bin 0 -> 160 bytes .../abs__btn_cab_done_default_holo_dark.9.png | Bin 0 -> 109 bytes ...abs__btn_cab_done_default_holo_light.9.png | Bin 0 -> 108 bytes .../abs__btn_cab_done_focused_holo_dark.9.png | Bin 0 -> 112 bytes ...abs__btn_cab_done_focused_holo_light.9.png | Bin 0 -> 113 bytes .../abs__btn_cab_done_pressed_holo_dark.9.png | Bin 0 -> 115 bytes ...abs__btn_cab_done_pressed_holo_light.9.png | Bin 0 -> 113 bytes ...abs__cab_background_bottom_holo_dark.9.png | Bin 0 -> 166 bytes ...bs__cab_background_bottom_holo_light.9.png | Bin 0 -> 161 bytes .../abs__cab_background_top_holo_dark.9.png | Bin 0 -> 174 bytes .../abs__cab_background_top_holo_light.9.png | Bin 0 -> 161 bytes .../abs__dialog_bottom_holo_dark.9.png | Bin 0 -> 1263 bytes .../abs__dialog_bottom_holo_light.9.png | Bin 0 -> 1256 bytes .../abs__ic_ab_back_holo_dark.png | Bin 0 -> 741 bytes .../abs__ic_ab_back_holo_light.png | Bin 0 -> 661 bytes .../abs__ic_cab_done_holo_dark.png | Bin 0 -> 970 bytes .../abs__ic_cab_done_holo_light.png | Bin 0 -> 915 bytes ..._ic_menu_moreoverflow_normal_holo_dark.png | Bin 0 -> 167 bytes ...ic_menu_moreoverflow_normal_holo_light.png | Bin 0 -> 184 bytes .../abs__list_divider_holo_dark.9.png | Bin 0 -> 83 bytes .../abs__list_divider_holo_light.9.png | Bin 0 -> 83 bytes .../abs__list_focused_holo.9.png | Bin 0 -> 163 bytes .../abs__list_longpressed_holo.9.png | Bin 0 -> 158 bytes .../abs__list_pressed_holo_dark.9.png | Bin 0 -> 163 bytes .../abs__list_pressed_holo_light.9.png | Bin 0 -> 163 bytes ...bs__list_selector_disabled_holo_dark.9.png | Bin 0 -> 190 bytes ...s__list_selector_disabled_holo_light.9.png | Bin 0 -> 188 bytes .../abs__menu_dropdown_panel_holo_dark.9.png | Bin 0 -> 1362 bytes .../abs__menu_dropdown_panel_holo_light.9.png | Bin 0 -> 1551 bytes .../abs__progress_bg_holo_dark.9.png | Bin 0 -> 174 bytes .../abs__progress_bg_holo_light.9.png | Bin 0 -> 172 bytes .../abs__progress_primary_holo_dark.9.png | Bin 0 -> 1309 bytes .../abs__progress_primary_holo_light.9.png | Bin 0 -> 1309 bytes .../abs__progress_secondary_holo_dark.9.png | Bin 0 -> 184 bytes .../abs__progress_secondary_holo_light.9.png | Bin 0 -> 184 bytes .../abs__spinner_20_inner_holo.png | Bin 0 -> 1160 bytes .../abs__spinner_20_outer_holo.png | Bin 0 -> 1007 bytes .../abs__spinner_ab_default_holo_dark.9.png | Bin 0 -> 395 bytes .../abs__spinner_ab_default_holo_light.9.png | Bin 0 -> 394 bytes .../abs__spinner_ab_disabled_holo_dark.9.png | Bin 0 -> 381 bytes .../abs__spinner_ab_disabled_holo_light.9.png | Bin 0 -> 381 bytes .../abs__spinner_ab_focused_holo_dark.9.png | Bin 0 -> 680 bytes .../abs__spinner_ab_focused_holo_light.9.png | Bin 0 -> 671 bytes .../abs__spinner_ab_pressed_holo_dark.9.png | Bin 0 -> 609 bytes .../abs__spinner_ab_pressed_holo_light.9.png | Bin 0 -> 602 bytes .../abs__tab_selected_focused_holo.9.png | Bin 0 -> 147 bytes .../abs__tab_selected_holo.9.png | Bin 0 -> 153 bytes .../abs__tab_selected_pressed_holo.9.png | Bin 0 -> 147 bytes .../abs__tab_unselected_focused_holo.9.png | Bin 0 -> 148 bytes .../abs__tab_unselected_holo.9.png | Bin 0 -> 166 bytes .../abs__tab_unselected_pressed_holo.9.png | Bin 0 -> 149 bytes .../drawable/abs__btn_cab_done_holo_dark.xml | 25 + .../drawable/abs__btn_cab_done_holo_light.xml | 25 + .../abs__ic_menu_moreoverflow_holo_dark.xml | 18 + .../abs__ic_menu_moreoverflow_holo_light.xml | 18 + .../abs__item_background_holo_dark.xml | 27 + .../abs__item_background_holo_light.xml | 27 + ...lector_background_transition_holo_dark.xml | 20 + ...ector_background_transition_holo_light.xml | 20 + .../drawable/abs__list_selector_holo_dark.xml | 28 + .../abs__list_selector_holo_light.xml | 29 + .../abs__progress_horizontal_holo_dark.xml | 32 + .../abs__progress_horizontal_holo_light.xml | 32 + .../drawable/abs__progress_medium_holo.xml | 34 + .../drawable/abs__spinner_ab_holo_dark.xml | 25 + .../drawable/abs__spinner_ab_holo_light.xml | 25 + .../drawable/abs__tab_indicator_ab_holo.xml | 34 + .../res/drawable/abs__tab_indicator_holo.xml | 34 + .../abs__action_mode_close_item.xml | 40 + .../sherlock_spinner_dropdown_item.xml | 26 + .../res/layout-v14/sherlock_spinner_item.xml | 26 + .../layout-xlarge/abs__screen_action_bar.xml | 50 + .../abs__screen_action_bar_overlay.xml | 49 + .../res/layout/abs__action_bar_home.xml | 38 + .../res/layout/abs__action_bar_tab.xml | 7 + .../layout/abs__action_bar_tab_bar_view.xml | 6 + .../res/layout/abs__action_bar_title_item.xml | 50 + .../layout/abs__action_menu_item_layout.xml | 56 + .../res/layout/abs__action_menu_layout.xml | 23 + .../res/layout/abs__action_mode_bar.xml | 24 + .../layout/abs__action_mode_close_item.xml | 31 + .../res/layout/abs__dialog_title_holo.xml | 46 + .../layout/abs__list_menu_item_checkbox.xml | 26 + .../res/layout/abs__list_menu_item_icon.xml | 28 + .../res/layout/abs__list_menu_item_layout.xml | 59 + .../res/layout/abs__list_menu_item_radio.xml | 24 + .../layout/abs__popup_menu_item_layout.xml | 60 + .../res/layout/abs__screen_action_bar.xml | 57 + .../layout/abs__screen_action_bar_overlay.xml | 56 + .../res/layout/abs__screen_simple.xml | 38 + ...abs__screen_simple_overlay_action_mode.xml | 36 + .../layout/sherlock_spinner_dropdown_item.xml | 26 + .../res/layout/sherlock_spinner_item.xml | 26 + .../res/values-land/abs__dimens.xml | 33 + .../abs__dimens.xml | 33 + .../abs__dimens.xml | 33 + .../abs__dimens.xml | 33 + .../abs__dimens.xml | 36 + .../res/values-large/abs__dimens.xml | 29 + .../res/values-sw600dp/abs__bools.xml | 19 + .../res/values-sw600dp/abs__dimens.xml | 38 + .../res/values-v11/abs__themes.xml | 12 + .../res/values-v14/abs__styles.xml | 118 ++ .../res/values-v14/abs__themes.xml | 28 + .../res/values-w360dp/abs__dimens.xml | 22 + .../res/values-w480dp/abs__bools.xml | 22 + .../res/values-w480dp/abs__config.xml | 29 + .../res/values-w500dp/abs__dimens.xml | 22 + .../res/values-w600dp/abs__dimens.xml | 22 + .../res/values-xlarge/abs__dimens.xml | 45 + .../res/values/abs__attrs.xml | 355 ++++ .../res/values/abs__bools.xml | 22 + .../res/values/abs__colors.xml | 27 + .../res/values/abs__config.xml | 43 + .../res/values/abs__dimens.xml | 50 + com_actionbarsherlock/res/values/abs__ids.xml | 26 + .../res/values/abs__strings.xml | 29 + .../res/values/abs__styles.xml | 344 ++++ .../res/values/abs__themes.xml | 208 +++ .../actionbarsherlock/ActionBarSherlock.java | 778 +++++++++ .../com/actionbarsherlock/app/ActionBar.java | 947 ++++++++++ .../app/SherlockActivity.java | 253 +++ .../app/SherlockDialogFragment.java | 72 + .../app/SherlockExpandableListActivity.java | 253 +++ .../app/SherlockFragment.java | 72 + .../app/SherlockFragmentActivity.java | 347 ++++ .../app/SherlockListActivity.java | 253 +++ .../app/SherlockListFragment.java | 72 + .../app/SherlockPreferenceActivity.java | 253 +++ .../internal/ActionBarSherlockCompat.java | 1187 +++++++++++++ .../internal/ActionBarSherlockNative.java | 318 ++++ .../internal/ResourcesCompat.java | 95 + .../internal/app/ActionBarImpl.java | 1026 +++++++++++ .../internal/app/ActionBarWrapper.java | 462 +++++ .../nineoldandroids/animation/Animator.java | 278 +++ .../animation/AnimatorListenerAdapter.java | 54 + .../animation/AnimatorSet.java | 1111 ++++++++++++ .../animation/FloatEvaluator.java | 42 + .../animation/FloatKeyframeSet.java | 136 ++ .../animation/IntEvaluator.java | 42 + .../animation/IntKeyframeSet.java | 135 ++ .../nineoldandroids/animation/Keyframe.java | 361 ++++ .../animation/KeyframeSet.java | 227 +++ .../animation/ObjectAnimator.java | 491 ++++++ .../animation/PropertyValuesHolder.java | 1012 +++++++++++ .../animation/TypeEvaluator.java | 44 + .../animation/ValueAnimator.java | 1265 ++++++++++++++ .../nineoldandroids/view/NineViewGroup.java | 79 + .../view/animation/AnimatorProxy.java | 205 +++ .../widget/NineFrameLayout.java | 65 + .../widget/NineLinearLayout.java | 65 + .../internal/view/ActionProviderWrapper.java | 40 + .../internal/view/StandaloneActionMode.java | 148 ++ .../view/View_HasStateListenerSupport.java | 6 + .../View_OnAttachStateChangeListener.java | 8 + .../internal/view/menu/ActionMenu.java | 264 +++ .../internal/view/menu/ActionMenuItem.java | 278 +++ .../view/menu/ActionMenuItemView.java | 295 ++++ .../view/menu/ActionMenuPresenter.java | 715 ++++++++ .../internal/view/menu/ActionMenuView.java | 572 ++++++ .../internal/view/menu/BaseMenuPresenter.java | 231 +++ .../internal/view/menu/ListMenuItemView.java | 278 +++ .../internal/view/menu/MenuBuilder.java | 1335 ++++++++++++++ .../internal/view/menu/MenuItemImpl.java | 647 +++++++ .../internal/view/menu/MenuItemMule.java | 234 +++ .../internal/view/menu/MenuItemWrapper.java | 285 +++ .../internal/view/menu/MenuMule.java | 150 ++ .../internal/view/menu/MenuPopupHelper.java | 376 ++++ .../internal/view/menu/MenuPresenter.java | 148 ++ .../internal/view/menu/MenuView.java | 120 ++ .../internal/view/menu/MenuWrapper.java | 180 ++ .../internal/view/menu/SubMenuBuilder.java | 134 ++ .../internal/view/menu/SubMenuWrapper.java | 72 + .../internal/widget/AbsActionBarView.java | 291 ++++ .../internal/widget/ActionBarContainer.java | 245 +++ .../internal/widget/ActionBarContextView.java | 518 ++++++ .../internal/widget/ActionBarView.java | 1548 +++++++++++++++++ .../internal/widget/CapitalizingButton.java | 40 + .../internal/widget/CapitalizingTextView.java | 40 + .../widget/FakeDialogPhoneWindow.java | 64 + .../internal/widget/IcsAbsSpinner.java | 479 +++++ .../internal/widget/IcsAdapterView.java | 1160 ++++++++++++ .../internal/widget/IcsLinearLayout.java | 160 ++ .../internal/widget/IcsListPopupWindow.java | 640 +++++++ .../internal/widget/IcsProgressBar.java | 1193 +++++++++++++ .../internal/widget/IcsSpinner.java | 699 ++++++++ .../internal/widget/IcsView.java | 21 + .../widget/ScrollingTabContainerView.java | 545 ++++++ .../actionbarsherlock/view/ActionMode.java | 224 +++ .../view/ActionProvider.java | 170 ++ .../view/CollapsibleActionView.java | 39 + .../src/com/actionbarsherlock/view/Menu.java | 447 +++++ .../actionbarsherlock/view/MenuInflater.java | 472 +++++ .../com/actionbarsherlock/view/MenuItem.java | 598 +++++++ .../com/actionbarsherlock/view/SubMenu.java | 110 ++ .../com/actionbarsherlock/view/Window.java | 65 + .../internal/ManifestParsingTest.java | 39 + org_apg/AndroidManifest.xml | 5 +- org_apg/project.properties | 1 + org_apg/res/layout/main.xml | 1 - org_apg/res/values/colors.xml | 82 - org_apg/res/values/strings.xml | 4 +- org_apg/res/values/styles.xml | 1 - org_apg/src/org/apg/ui/BaseActivity.java | 8 +- org_apg/src/org/apg/ui/EditKeyActivity.java | 3 +- org_apg/src/org/apg/ui/KeyListActivity.java | 5 +- .../apg/ui/KeyServerPreferenceActivity.java | 13 +- org_apg/src/org/apg/ui/MainActivity.java | 156 +- .../src/org/apg/ui/PublicKeyListActivity.java | 7 +- .../src/org/apg/ui/SecretKeyListActivity.java | 8 +- .../apg/ui/SelectPublicKeyListActivity.java | 4 +- .../apg/ui/SelectSecretKeyListActivity.java | 4 +- 368 files changed, 31904 insertions(+), 184 deletions(-) create mode 100644 com_actionbarsherlock/.gitignore create mode 100644 com_actionbarsherlock/AndroidManifest.xml create mode 100644 com_actionbarsherlock/README.md create mode 100644 com_actionbarsherlock/build.xml create mode 100644 com_actionbarsherlock/libs/android-support-v4.jar create mode 100644 com_actionbarsherlock/pom.xml create mode 100644 com_actionbarsherlock/project.properties create mode 100644 com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_dark.xml create mode 100644 com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_light.xml create mode 100644 com_actionbarsherlock/res/color/abs__primary_text_holo_dark.xml create mode 100644 com_actionbarsherlock/res/color/abs__primary_text_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_inverse_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_solid_shadow_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_solid_inverse_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ab_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_default_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_default_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_focused_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_focused_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_bottom_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_bottom_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_top_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_top_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__dialog_full_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__dialog_full_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ic_ab_back_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ic_ab_back_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ic_cab_done_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ic_cab_done_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_divider_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_divider_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_longpressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_selector_disabled_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__list_selector_disabled_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__menu_dropdown_panel_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__menu_dropdown_panel_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__progress_bg_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__progress_bg_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__progress_primary_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__progress_primary_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__progress_secondary_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__progress_secondary_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_48_inner_holo.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_48_outer_holo.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_default_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_default_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_disabled_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_disabled_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_focused_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_focused_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_pressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_pressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_solid_inverse_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_shadow_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_inverse_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ab_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_default_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_default_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_focused_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_focused_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__cab_background_bottom_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__cab_background_bottom_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__cab_background_top_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__cab_background_top_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__dialog_bottom_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__dialog_bottom_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ic_ab_back_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ic_ab_back_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ic_cab_done_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ic_cab_done_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__ic_menu_moreoverflow_normal_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_divider_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_divider_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_longpressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_selector_disabled_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__list_selector_disabled_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__menu_dropdown_panel_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__menu_dropdown_panel_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__progress_bg_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__progress_bg_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__progress_primary_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__progress_primary_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__progress_secondary_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__progress_secondary_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_48_inner_holo.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_48_outer_holo.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_default_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_default_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_disabled_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_disabled_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_focused_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_focused_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_pressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_pressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-v11/abs__progress_medium_holo.xml create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_inverse_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_shadow_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_inverse_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_transparent_dark_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ab_transparent_light_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_default_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_default_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_focused_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_focused_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_bottom_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_bottom_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_top_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_top_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__dialog_bottom_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__dialog_bottom_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ic_ab_back_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ic_ab_back_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ic_cab_done_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ic_cab_done_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_light.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_divider_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_divider_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_longpressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_selector_disabled_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__list_selector_disabled_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__menu_dropdown_panel_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__menu_dropdown_panel_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__progress_bg_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__progress_bg_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__progress_primary_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__progress_primary_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__progress_secondary_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__progress_secondary_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_20_inner_holo.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_20_outer_holo.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_default_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_default_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_disabled_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_disabled_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_focused_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_focused_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_pressed_holo_dark.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_pressed_holo_light.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_pressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__tab_unselected_focused_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__tab_unselected_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable-xhdpi/abs__tab_unselected_pressed_holo.9.png create mode 100644 com_actionbarsherlock/res/drawable/abs__btn_cab_done_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__btn_cab_done_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__item_background_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__item_background_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__list_selector_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__list_selector_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__progress_medium_holo.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_dark.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_light.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__tab_indicator_ab_holo.xml create mode 100644 com_actionbarsherlock/res/drawable/abs__tab_indicator_holo.xml create mode 100644 com_actionbarsherlock/res/layout-large/abs__action_mode_close_item.xml create mode 100644 com_actionbarsherlock/res/layout-v14/sherlock_spinner_dropdown_item.xml create mode 100644 com_actionbarsherlock/res/layout-v14/sherlock_spinner_item.xml create mode 100644 com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar.xml create mode 100644 com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar_overlay.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_bar_home.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_bar_tab.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_bar_tab_bar_view.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_bar_title_item.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_menu_item_layout.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_menu_layout.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_mode_bar.xml create mode 100644 com_actionbarsherlock/res/layout/abs__action_mode_close_item.xml create mode 100644 com_actionbarsherlock/res/layout/abs__dialog_title_holo.xml create mode 100644 com_actionbarsherlock/res/layout/abs__list_menu_item_checkbox.xml create mode 100644 com_actionbarsherlock/res/layout/abs__list_menu_item_icon.xml create mode 100644 com_actionbarsherlock/res/layout/abs__list_menu_item_layout.xml create mode 100644 com_actionbarsherlock/res/layout/abs__list_menu_item_radio.xml create mode 100644 com_actionbarsherlock/res/layout/abs__popup_menu_item_layout.xml create mode 100644 com_actionbarsherlock/res/layout/abs__screen_action_bar.xml create mode 100644 com_actionbarsherlock/res/layout/abs__screen_action_bar_overlay.xml create mode 100644 com_actionbarsherlock/res/layout/abs__screen_simple.xml create mode 100644 com_actionbarsherlock/res/layout/abs__screen_simple_overlay_action_mode.xml create mode 100644 com_actionbarsherlock/res/layout/sherlock_spinner_dropdown_item.xml create mode 100644 com_actionbarsherlock/res/layout/sherlock_spinner_item.xml create mode 100644 com_actionbarsherlock/res/values-land/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-large-hdpi-1024x600/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-large-land-hdpi-1024x600/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-large-land-mdpi-1024x600/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-large-mdpi-1024x600/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-large/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-sw600dp/abs__bools.xml create mode 100644 com_actionbarsherlock/res/values-sw600dp/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-v11/abs__themes.xml create mode 100644 com_actionbarsherlock/res/values-v14/abs__styles.xml create mode 100644 com_actionbarsherlock/res/values-v14/abs__themes.xml create mode 100644 com_actionbarsherlock/res/values-w360dp/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-w480dp/abs__bools.xml create mode 100644 com_actionbarsherlock/res/values-w480dp/abs__config.xml create mode 100644 com_actionbarsherlock/res/values-w500dp/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-w600dp/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values-xlarge/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values/abs__attrs.xml create mode 100644 com_actionbarsherlock/res/values/abs__bools.xml create mode 100644 com_actionbarsherlock/res/values/abs__colors.xml create mode 100644 com_actionbarsherlock/res/values/abs__config.xml create mode 100644 com_actionbarsherlock/res/values/abs__dimens.xml create mode 100644 com_actionbarsherlock/res/values/abs__ids.xml create mode 100644 com_actionbarsherlock/res/values/abs__strings.xml create mode 100644 com_actionbarsherlock/res/values/abs__styles.xml create mode 100644 com_actionbarsherlock/res/values/abs__themes.xml create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/ActionBarSherlock.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/ActionBar.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockActivity.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockDialogFragment.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockExpandableListActivity.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragment.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListActivity.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListFragment.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/ResourcesCompat.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Animator.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorListenerAdapter.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorSet.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatEvaluator.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatKeyframeSet.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntEvaluator.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntKeyframeSet.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Keyframe.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/KeyframeSet.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ObjectAnimator.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/PropertyValuesHolder.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/TypeEvaluator.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ValueAnimator.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/NineViewGroup.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/animation/AnimatorProxy.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/ActionProviderWrapper.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/StandaloneActionMode.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemMule.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuMule.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/AbsActionBarView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContextView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingButton.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/FakeDialogPhoneWindow.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAbsSpinner.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAdapterView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsListPopupWindow.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsSpinner.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/ActionMode.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/ActionProvider.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/CollapsibleActionView.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/Menu.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/MenuInflater.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/MenuItem.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/SubMenu.java create mode 100644 com_actionbarsherlock/src/com/actionbarsherlock/view/Window.java create mode 100644 com_actionbarsherlock/test/com/actionbarsherlock/internal/ManifestParsingTest.java delete mode 100644 org_apg/res/values/colors.xml diff --git a/com_actionbarsherlock/.gitignore b/com_actionbarsherlock/.gitignore new file mode 100644 index 000000000..2e423e1a3 --- /dev/null +++ b/com_actionbarsherlock/.gitignore @@ -0,0 +1,23 @@ +#Android generated +bin +gen +obj +libs/armeabi +lint.xml +local.properties + +#Eclipse +.project +.classpath +.settings + +#IntelliJ IDEA +.idea +*.iml + +#Maven +target +release.properties + +#Mac +.DS_Store \ No newline at end of file diff --git a/com_actionbarsherlock/AndroidManifest.xml b/com_actionbarsherlock/AndroidManifest.xml new file mode 100644 index 000000000..a1f0e16ee --- /dev/null +++ b/com_actionbarsherlock/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/com_actionbarsherlock/README.md b/com_actionbarsherlock/README.md new file mode 100644 index 000000000..4b22e6fe2 --- /dev/null +++ b/com_actionbarsherlock/README.md @@ -0,0 +1,15 @@ +ActionBarSherlock Library +========================= + +This folder contains the main library which should be linked against as an +Android library project in your application. + +For more information see the "Including In Your Project" section of the +[download page][1]. + + + + + + + [1]: http://actionbarsherlock.com/download.html diff --git a/com_actionbarsherlock/build.xml b/com_actionbarsherlock/build.xml new file mode 100644 index 000000000..3948e7c6a --- /dev/null +++ b/com_actionbarsherlock/build.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/libs/android-support-v4.jar b/com_actionbarsherlock/libs/android-support-v4.jar new file mode 100644 index 0000000000000000000000000000000000000000..d006198e6abd489276ad3c69eec894d141c2289f GIT binary patch literal 247894 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

^2O9}u)h%ALZET*B+UYGgPJD|JA7hVoH1aNO6 z7ElC-zzb}zQvYWGs}6{?5P)eB z;#(qEX%h@I5^1Mt>U33tnn!UCA)tc(G;PkI4w(lPZeLz9I~pf5OO6$oHw{hxa)zvh zj+3p{Z&A=}ApT~Rj!S<|Z@D<*zXt3J^;R$zDX*NY5Yj`*9>#x}JqpX?= z7O=$wCH2nvjd{lCsH*3y>w~LdOsPv9#h?{0YDI8cWyIO0e$FYH8=I6Af@Oxe40Wn_ zh^D@kVml0}71HlPGt72LA!lrih8AJt*h!dNDh3+BoBZF=0U=MfflfH{9K&jnxgeB0D4sj#dpi`^ z`anDD8lHdM9HYE7dIb(xUy|y9yalklMR2_N89pMGpQAUeI%`i$MAOgU8^yCoa@gMf zLCp{(AY{mfV5pLf075tDXHL05ya&){ktWEy@YD8&h3}#O+tW@fq7}0Z+iTFq{sQ|8 z+p?<RQ;h#FQ`i3%K!`SQR5#qt9s7BRr$;rz_Q@dMv-+40og zp?uw%I6pr(nl}i4d>UuI!@T;~e&~EMdOIM8;Q-PpEQy&70C7n<*sb?M!#dc-MLpip z^?J*Q2y_{UM)Jrijuvx~5F*8jz5WjV=bzMExVzzWW7mXP9HZ;fqR({!#fAzyGF3&{P9^=uB&rK3M+p*W0*j>c?2pv29PpEGo9(%!F6~obD zH+WtX^T#gFU@+W=_}uZ=_Ne13OGNK{ebnE+jnyu<=3w17`_0dp5kl`-%AYv9(G*MB z1n;VS;y-`O@U>N?R?;$JE${ujQ`^gTD^QNSI-A`aRpIL!|7qWhe#A9-E_&M5#$GgA z8k036p&4nZ^x&IfB=#K6Bg&FX6gR+ccD2?}-fS4prZ3{u-yHa; z0nLpg_cYYfjH2}ATKT8R#VOe3>E)@}HX`%|D0qkru4|c!j+gK_aBfsr)IE{41{qE^ zHl)*)nJJ8Ay%G>@=o55m8e5HQBmtzoLe7$qG*Mn{P)M$DTv6#sXi$W_d1}=(9MnFQ8!hRGSkoI1g^h2AU)Zxvh?h4?s2|NeSy6iGQOGo|e z^lc%I!#DEGPfq1UY#7?>=EK#7I;JlAbC2_+t(Ioi^z3Y!-1DhN5?iG;))T^J=^=j- zb+dx+KZdw0b<}Jabs>fEp(vO10Rzj6P6ad&7W2V;`+DXq8B7Tw+tM#&HP0lVua+jX z-kOqz8wU`yGtp9TJ5mf?+hJw(*A?i;GBBJ3(J*t*wkCGYOtVnJ;$Dr9a=AjaQb*k! z2nN0Eu{`FsE}0^E<-?6%JM9KSh|gV3q>E#1Dm!R)TEjKlpUh$d3avv;F{dOnrnm^h z0L?i1RrNqLOAvB+OmQBjIu00GdQMt@*z1b)7X+}0IF;C3=nSBH}aGbl0=$TRl@5HI0KLm}I3Cnd(Ps5Zhn8rwp6Wnk` zwO*GxLr0ZyEu|yIdo$!t2A8wqjRG`y&NTgs#{-y7C>4k5R9wVYqZpDRj~wEpZ(}{EqinNJn2Zryr(`ry zL@%QR_xVYO4hlFEdiVJeip&W3$Q#R*Dt`ZgInWO&bX_d>^CzD(iHul^M_!}2lkM$I zG)-BAoWC{GZ7M{~8ie$mj7^dMS}97Vd|y7*eH6P4)6E4`*&*t=?Wdkj7nT!8kBcIIiu&veW~VoNKzGfhJc|%MBD|(R@K> z_^(n@C8AM`W~T7`_iB=inz;i%PxR^r>dZ!LkwuZ0Y|MM`zDhQ$&EC#6d#8ArW@bSM zvHx)3j|`uIolYRniR1KSC8I!8CktkUu|z2MlOq=Z^U7K+DXKmP(VWUUI1qRl<#b?vaJ zcfszPW%kr?t4Z$hF`Pp+35$GrDAQK(_?ls3xFjeCiF9g%Sr%8HUBv2yV=wA9O^1I* zLoB+ITk>oOv16K_9h*Z!LV@ySC$B6lzi=_6Zh9)$#6S88oQdU-mGB}U10@9t8Vh24 z@_E^XV4879U~0XA9dC3#IGk-;XSm2>HIQ}H>I;rua4Wl`mDs)8(^g2-3 z`r!eY=;WGUH|+4(=6#?by51b}Mg5`NZVpTN<#oxImyJ%KR@L>IdlNNeP`2ik_Prv2 zG@M=**bPqyNA@&-y1+P(WEOD|%iDelNEC-z#t*LVNU|D6X9-@;8e@mnnZ5j4Q*Zr~ zyMx{(j=wr{@XQ-Hsj#d-rQMT>*}3dk?nBL6OUpB@70^v&PTDpERg)lTHl3)onylDD zX`g3WhShBO8f0fFHzCS~;{W?6kac4VhfRn_X2vB^s@^Eqx|!fwCevEL_qO z`rN+uCB#7~YHD+vav{=9Ih3r-pTG_7fidE>LG{$G%5_86fPzYW7Hp6a#o#pgJY!KZ zaXa04QG%963fK;$A`STfqHWdR1;)fKzEqOC97Wj?XHe9H^tq*7Kv?U(3#rw*U%rmq zaXX~8PPZxfg~#K@{Dn4C%`{Lk6@iW`BKtSdXO4EQSpjl zu*62epjLgq+JCd&9|tEZl$2xamD=w3IhY^XrnFX9y>3iw4Myb>nBbl5|tdoH%Ka$-d z70*V&&8QZk+i&}AnM}d*PovW5U1rz~8>Wxp0$kw~{VY<4KGp}aX|j_-Z8+XQbNNWD z=>k4Y$TdyS6n%JQaLhT9YauuN7Poh{pbeqy*&(eQN)b&VLS?o@Wz0&;sl)^oBXHvT zK*Ia%;E`PAX|SA$N`S$C-Y1mPHDZJsl%1$IeK8WtHwd?FpPgEpi7fkJ(fsN(T!G1p z;Sts1vo@$dDtwrEm>qfFkrmmYqlA}s=1|@zSNclytk8U{U|^65^rqMix2JnJpje9= zD5EOOEAK?I4(GZMD?9{gs~RIFMg=P!Lny72;f#*7gQgH{#0jt13a=Ge=fk;$~^8mIR-=;)E?`$)v7eMW#VHYNUTDMErx{~ zPsUk0VA0xu`{fm+TXm!9uiN#4FItn5EHE#b>yk^>8=^_T&sxbh>W1MR@4uV>)`hIf zPCl1ZXo2O=5%GVPEYkf=9U{;BtFH8|fJ$S98YmxG@KZnjD%FhcW!`k35U}Wp z;Oui1yU8LY!}tX9C5p?XzE8LFFNI>5)c}NKS)l7uGV`XV)rQ-q+s*D_Tf*Q z7=kQIlv~*6DAf^EcJ5nQ2imE4nb7zeW^^3q)@a1!5UH+=m?eZ38Q6@#MDlpEhQc5m zp2?)xpVbRBs9m%OrH7wY2y<^)d$XBapW_2(`egWhjyVI=)%<;Qsu4u54DD?xW1L=C zi1W-Ve_RpLAJQc;JTiE7bH>OKo+X)4fw3mBX0LE5#<7x6DHY@mRMFxxB5P!Sokmk? z7hOSZAkvq%&&;MjW9GjqM@Q^>$%7)(SdpaWmb(0Ry}_mJ|8sPhVB zY_nb9pq7YgDuo0d*T#3D#DP)=&Y(+`q6l_C0(c;?-=2xG(#HFgM7%cuoa>GJ4P@!t z9+&QhmAdXZn;eo|9dVOCVk)W*q|s7$VDQj}qCQM%U#@qr#YtxXd)?tt{APa*Rm8^8 z#nxXsi+NJ1#GimWcEmp_34hIk`VZru{;s$Gw^)?$o0Y8@KnUw#X{n=YZt!=I{Z+hN ztD&q+F?YRf1QHKQ7*HQf(;|s5)7Pgw$KwmbA=!w4xirC4TPxs+EKSG(Xjp;q2D6(k zMMp+7a829nG}->JW_NM=^5XFb%!7r2CsyQtDGOPJtj*cQ5RmomZ?-XsmH@pv_!1{f z_Dy*D@Vr_?VvM$vCK8d^A9)YQQJsFf_DgY|34I)s7B=nA@-ktxP>bTe!kkT{mflt} zym`lj%UdrqlTP6*s}9lQrpvFhBzzZ0Vyta1ykyyGWRrHhS54!J(nd8X`~BKcCJ5;r z!7&nO*%Ec)U+PzeClzM4ABP86P{`D=F5H=#jyv4kRtDyQ*xc}GKWLI5k7EP} z%#A$t0^Wk2cwUe^$dzh9oZs#d{RS@}dKAk>Vk=N0Ai4JB;x?&ko^%k z%>Vz8^Q(WggWcZ{qhR*C+?~I1-9iPs0f}l0qn>I+)uo6G50-Q(4|x|zRPTkBl7gDL zR=<_#N=sC<&zRBuUKs5HD*Pj%P;2U1D#L@#M0)x%4tJ+Js7y~SAtRz3sjm89R74=z ztsWoxXcte2Po%!kFptpF;OCkd>Ek5bDJ`XA@%bkR{+BI2?aqF6a zekD|AHKp46Mijs3dTs+&?;v$J6&fEZW<!kg@drH;&XKu?dDr%eC-l*I*LhD( zm7M887d^8$>DE6GeH`+3(ps$Q{< zhTezfeYu8V(zN z;sUzdU2y&%oy8JiTgAbtP4jfXBB?_nv4Sbt@29j-HiIyJ3UM@P=m47xI^fG`yf}eC z%@#hWU_D+7;=GsnUAqULOLJ>*S#Xi;1MM+jWyX72E1!pr@xXtxMuKAHYaAXWng9Ik zm!a3k1+R-roGMfOc|TISE|xnSyM86rTj?e2Cmo~8lwH9Vy2=gVmqi=?eiMS>gSmG+ zA(||dLE*?KmY?FyQcoi6=S69Bs7>Pc0>R?3Lq^*abwa9fRlF z%zV{MSaSgjLY3=5jnhUrK3Fflyf`d6C=a3%W&ECzQMWGYF{^?|=-ArUTo1oT z;DsxJ6B+>Warb{j^8e4WwZEOM@$wb`5W{*Ri&dr-<|B2JlJm_sv|*C^0GURK%;Xv4 z=YiuSo)lneKBTmBLcNP2SRjbLe0(F_J;jpk8rULgWq&wrtsc~>Jh>wf`l=L z+S;bA*pBwE@ec{76k}BwXVs%N3THHGaKYfWrlM3{wAj)%WhF!5J`T)!q zayPZh>r1iulIeryu_X+pZEY$CQIB>>3hy_z*lb=hWz{0P_QOxCRaiVAj7g$_%r6FG zy8719qE;R^;_aZg1kA^vbU z1?}_Cw)URgtMe^XcC7L-5^_D)Mlu_CW$QhCZ#!hgzqp6-7q+0AXU>f~Kz^_? zVc~i+Bv1ASpr`H7*D9Xggu*whY(+-FJa5b_K}nvWFEN^U{O~eG_gy6{fbMB@cQiMhxX!S2TOPN-w zULM#qNlYkW8X$N+1$&?A)B? zWvuZD)BBr~SKuxu>(47q;vdgj z)LAS7Dp`CjEmlh7U#)%}n>Sxwf{+2nW;EdUYvup5)lmPl)f`O>oc>@h(m#Bq1o-ry zEmWScDZi_LIM~@p6&D}_(bNTmUslw0YzTKbi0tGqw9 za;>?!XEM8Y9-F;a;B?-&FZ}g#ExM~E<59|5CiO>JvgUWU`iK_{eI5bLx~{Ep(iVsG z$uqVG6W3B1-osFwWV2OR{njBNnu@)3!n9$`YNKZP3e;*MMvxm48ESM8&CBGe1s}h| z=>`p@s-XouhUqF$%9n~b-feZ&VHP*w&XdyqAZ#g~^(IDROJwA?v!wScGz#exOGb*% zX3j-3ZD{E%RRuBlR+#)2rlNG4qif=RDk+pE{gth_*se@MT!Wkf;K7nxJXh=L0t>cD zTi(2Mj9a!ht;I$doPP9~LIGQ7()W?4yx{XB1plH1R@tNAQCcH&}1=mMtM)EjPxqQYx=$DYz0} zaI+1rSeG{q2_kF5-GRtJu|c8{_6u8PfOd!t|XWtV%)0X4_j(YKY)tiK79b;mcie;`>;i?1U4s_v>VcXk?(Lt z*Ahz*{L@KxD$sssA<56z+@XT&d%u5*giyg!+B-FNe zi-4A_$9Kuc_X1foIPytFF~SG&R@*O$B9k)~us`Uz_`CZ>VPd4Bu<>ofhI@9RxX#yo*PPGGdyOS7EI6Sb{LC{)lo7ZWt5i!6-w~s09rYnBt+lc z89ppkwicyUVri`dZ$kT6^DIeN9uuA%(iY4m+VU+yd0-o&Vd}+iAv#ro>V+ z$pvz$v$%^JHzGipdvzuDlgRMruGVgBZ}ezJ*!4M({e-ta=Bhb4wsx|GPK+0a(lP>e zHVf5;o|U!IfqBKXnyO0rniPd(UBKXvc7OO0b8zV@E@`)Sqlo*H&bq>)s`{$vjEt+X zEohH)pX?h{oN zDD@G?HIjP!evqm&bThh(;N>uuAKj;nzMC{fo3E1+wW81f(Ew%wjbVJpX+;EsB~w z+i1uG>CCgpK-erXhnn74Sd5>^7yH&UiOBrbYTxgS8)$@A*#cIm)4`To|AyAjABR{E z+2SQtSlUNR6ATP}(>?-`tl4c!!`$gIIiw$x?Qx6|K1zsLIHX~TlFO8@em{O5l+aRb zWO3(OogiNYd;Mt&=NbJoE$U>h4p=XgxHh3-n5}$Os8S=h25bguX-4zK>QXJHPWRAnqv`0(CC|eT?@(==)n#Qm%#IKl3oOcBKQUML-ln*axj`WJ*=bx2%z=3251~p ztbISflW?p4P5ODyHqaREeSuErdUwI0230E2@3i$VWA5K#I*_BNnL zt)YpLgRPD|z{~%W@Bd}&fBEl!9R%fa!(za^upSl(!E}~3p>6`*Uobz>27jv+$n2jp`wYW-B|EvCs5e4?aJtsDJ~m z>0kupLry}J5@&+lvBk<$dSHeW*fU0DipYsxLPmXgWCjGoDOML}n0$wB>TpulIcvp{ z#%K)4TXwIk&uBt`17Zkz*uIjT+MSE+GurFdpH$qVDlu<>bsDiQmBk<{!RL6t@iFY3 zuw7Hx;Ok)_dmgzShB&!=R6+fW45{2x<4xcD_#I}F7MUTL`dfs{LJPdo&{ShzCxd(X zRqx%*MaVQzd;3JIQ+7VV`ZyQ6AR5_!rh{Ra<1+H~8!WPP;du?5a03tP^l4?A1v z*S9onK(u~vHFYPbCqx&q;iCo(7qr$lWG*{e%|y-O{>A=Fx44Vgm65 z0^pF#YrnH0$?1b1BLIj9{39a%uPrX~YrZAnFDmdWH2iCehs%tL0n-86o>rQiEG;}a z=5O(ZqJDA;N%f(XQIKh|#><;kmM;(;mN;>0hFAaj(KF7OfeYD^(OTkl0a6SqSRf^jH(|qYVkE?N0q<(pMCWR?vY2{Z!G}QwdkXnmP=w zi<#Ki2@oIdpZ(+(p^>teXN3SwEP(ed_i$x3R|n{Sh!=Xif+W;{re7n;77#}_H~P`i z3pHEf&nnm)W|53fsc<6xFlVc$M!3CPrFrrpETnT0N6oP)O z<(sR9)sCHd>?F>ttR51Pm}05LHo<8eoe(!0*2kGLAp&&VM6h z|B;M^M^1_Xg7tw;6EitIGmyykkfqR2TuoPUZ$6$YnWOGiCM-sxLZlQ)q-5)C_?pUL(f+>x?=j=T}@I$t* zb~<);DE8JaT22=3+nooLy99?Q%b?)g=IFtCb82FogNu2qwT*eEv{Q~&?b&yten`}|mU~a` zg-R21{rvbMGf-PwudTMk=f1>QdzkiaHIdQ$^7Oh(;$^^r$pha^d2V3C9SRDSd?+`n z?T^%q#oZy(tAd$ zCS*QE7nRbKs?BGogNF3*2l67)nFo!3l zbzPB@AUa9(Pvyn)NRMx#>VU=>v}Kv9l@3?C^o0nz6q2DqhiS5-v=;W!_f>mh*p5^- z_kDgAs^K|r`lwuK#b<>IcPK9AaB?$JvOi&10=zZkAJnf@13;~hj_N4SZa(7(peIve zs-D?lF$ZkJ?vhqKq@`bTr6UFGW6PW+iJwI@JA)ZUWvvE334T-L=Del3jn9wfPw>y^ zztG+^j^e>-XIOX~BZd(IN9_`Uqf|XB98`5_sx}Bx zKrC8g1_bXK)XM74-owWvc(Nt1RVjR4gYq0i&$A2xae6~=>Glve>l2- z38-N`P6!<_ps2ttygz}x;%|HT_kpO|fD6`(jAkoBt5311tdyUw4>|gbSnc!#hx}?t z?4RTyp_)@P;_Yj+5w_{Zj(t(N$?XZ`9ASvYbZo%U#gBlUJY`*VuHD;B?AuNeFQLVN zm{Ly7UtRv}qf<-}&w?rB9Te#TD_1x(&57;kLMfTf9QvbJ0`>S=HTWhY!pCXH-HK3Q z1<3}#{yLX;XzNKVxC!f;5Uy|I#Ckrm{?u*Qns&2U$c&V2?-p+qX7yOF$&!(s39QR$dAR58g-D-YV2(Tx?w^X!fu zrULPSsw5psUoDxjVWbrPSv;8NMV?XYZF+P@@N^G<(MY z4M$qA=E$X~)NZope6ofW*XVE&)anh{6i;vkkuSefoe0cXXbC-DBz>;B>l4-~#z#nI z(iN2(0`-vdB(GHEg0Zm}yDRaL1eb5EUUv>+nEE^Oba*Hj3jOJ)5>JxN;}|=LVHHZ9 zJUT0fgf}K(Xb3p$4|K-iQG!Wtk73;*vjN-nzA8Uau0dTTlHtZ|K}{=Wl0 zb=k7H1%Qh@`6C4&`vaJM`Nh8|z~4Ghgv&@u1JS{Hfaph;1r_~tpf?7>R{y%es40UF z?n#J#4stA4Q+Zf3*d33Fsyd$!nETz;Jfb#$0*xc=z^k3zgj^PxiiQTW7Wx zng3p$+=K>oW?nTFPkxcJyJ{^)Y%L-XO9$J91V3+XMgzOPIeOURv!S`x0F?G+su;L@ zq%$TY-@TgpU06apE_S5Q%96G%xi!XL+h4D}xU}_W><|Q~u=C=}zAIc?{LQ5UzE=%{-UX=X%IG>BY<0A;D`*&*? z;kMkPOe@+E1x_em>d9jStaI<%_#uv`a0&QIPZO)z-y9*z7=qhge!f4>umg09z|@Y; z;ilrj78f~C>$(nemz)n)uioL-P$KvJam`L+m)_s1fCgm>%KSl;-tIE>v);pHUd~x== zkJLp3EjA{0iVzVeVKFFl52>_L;>K(*U2cv#7 zp-jPV!6JkP9+-;SQ>D0bu0zx+0Q){B|G^KONl?BAx}Z5#`vCz`A-S{+H*Sf-@OQ_s zEpM6h1;CnF|B*8A{vWIQr{u@~B*g&Z76AMh)8)F=X8>udC-(5hgL((yQF<1RO%nDwXtb-Vs z?pzdYz)UOZLMBnDQbn4$TM`QHmF9C4UkEJ`)LmAaY|}4ft{c;oQD`DB!1~9SI(EeK zlWCu1aaQwVuk`WMlJ!JLJLCCx)9z&JjMCjYn4sBS^?xwxTu3{z&;`6IkHWIRI({?c z6TiW=Jb(_9p==1O=Fg#yjzi4n zr?;28S5Pm`wxR4K{$L%o>G@S$T)n6$Ne;T(gyD;b+1^r-kI31##2PVP3GIeEWr;7YEIm7atKt*(fapUI%2 zK(%B##ECfl#0hxdluOudvSq(p;VW_5E6Aquj2&u~R4dJ(H8+eQp)(JU ze=Fj@R#an1doO&lW+g^N3xW#S#cZE5l|vd)^~fIe3$uuZ5KfA#5ppLK;V< znE#;cNG;-&NXl`c~!N$|7vPq1Y~yCiWN>_df!{Ym&*KDX!2WF`R#2YiU z-nzO>p>p9;p@dXid!X}dhbg2rsT^gUz3VXcz_mUFHc+{1CFBwKhJNdTE9A&Jnw1Si zo^TFVgLaZGZ_H=dxd>dOm$57sV&gvA%Uh~=}z0SiBe`y&z_B*0| z67V&8%}u~7UgMZou@%-73Se2C-%et~zwlI0uq>9`5m4k*YfEUMv5a&6+J=EZd_Guh zp^L~)jI9@_nn&>dNUex=Qe%0b@y(ev@VVnsQevk$n9xx&uWGc)aS^h4WS|dIla_L= z*bXgVC3J#zEgF7ZMwR(YOrSmYX5L_3Q9HowtCkXqOw=kJlg@~UK+8u6RB}ucHk?Fkh%~*2%8EC^v&=dP+iO;i; zlo}%59F+np2v>>f(8f%a$WxH4jLP9hTl~MGq#y8L39(!H&a9or9JEO>1b~#Q^f8p@ zAQyZOL_+=k+u8NNk3@L`j$`>hb}?f6e?m5)D8Kwa;rxG2jq*4Rc|aXwz^4C9-Pe)E zEFd7C1}}kqajF~{88-VVw6uw%@Yn)z4Jc6((a|oe>j8{2=WPJNV$F~=>t>SSVV~tR za@xE(&ERZ(d^~xE`2Z6gvJFw7m(=aCJT@W}JMN1*&K0wv4t1j(wUOg92$4bSm*`K_ zh7@q`j6Vu1w(K%7l?;cxNqu(MbfYwx%s3NU{bhaZI3ntGMSJL(R6XF_-jsymC-2Ax z&9SCyr!jqz$>@&M$ec!EtUg_?n#&-v%QzKMXv40_zDv0T-HOeY00Qywl-1Ef#P(7_ zvkCjoPy@$0BMjQ-9S2>kSP7C1hAMh~t1d!*MjA{@=bi?Ucw)u$^!*5uFmjinhK;5E zm|{2gdD^CvkThC9GKPkHXC>Cj zjVtZkZXJI_?RqO(!H>jbxPCB-i{?V8^ZhEk2dU6^wU8|Tx|jIv4e?1ny4yOOBb<_8 zmG17#DI}}KQ6AGNj3-Yb%{|DG5RMA5l@OJ4`rMfuC#M4M>S4f98V;wb;#^KXvls7o zt}u)yQ8)?BT3lT>0-J|5ftK2@JBH~j^1MeZy{IbMk=IXiF>F4oTn>y$E7KzNL8h6h zYyns34y8ijQj_k2O+fi@r?@~5pIB5ug-^Cf&$W9%rFSf0S>$-@;M9pPdBra|`$X3R z{BSt^HETHc`l|$(_)q}XP;if5er3CAPgTYS0K4V{Hh20I9f(K%x%Xc{Lt1CD+5Z?MyL_io> zN6f*amCf9K#BNWF$IyNRe4EY2*$Q713+_7ud6B{CHqp45ks1H~_)yvTac=snw(eKy zUX;(omj;%hbDtM3d5#xu2ss(6yd_!9hr=uPE=30Qu)bievN@uT?N0D+wOR@<`7l%w z`w~Z_Rn?8jST1YQT&8PMVctG_;sQ2JJwjk-(b-BM$C91jC_BFO)5+fL)D>p+aFucX zJ-HK_?UzpBLroJ_?C(r08zjJPn?&OZquf>N6<92`SQK_K=+OxxR3wONyC11ivB$qG zLi0mF01cq&E2KloGS=ya|J~{SzB4J@o8wlAoGXAF+QYbwuMZ zu5}wWs`Z6Oq$@3dt0EFR4}X)Trk&q}4p}Ie^yfxy#yX09`i)R(TY=Z1%<)JH7B)cR69I zi1as7p1K)nw?iVaUf~kjV!vjIm zeGE_=)=9OAsv8-jFy`mrH(f>_Sn`FVr0SlrW*=w)rWi1V3oL_Ru_>d@*!FF_Vv06W zgRr8mk+_74Bh!|}FCjD#(d?58dlB#lOOiwiJu3TbxO6Y#W0YG9L4Pz#ch00@IDb}J z*}b8G4L3WTx>&rF6vH*hiPVX-?}Ed?I4Qp(`Q&y5ZgM^o*27|81gvG|Lca3FJvaQX zF^YIYens)K&iYMpbUjhz-l8xPi)62cNCT~s9P zylhdsl*-4h7o0*=O}+q(BT_F(qV;f}Q@CzHcW}cmc5wUHUFx4bH_y{ot>5pd z;fIA)9D(!T)-FQg`P41FybBYC(HjTcGv+VRkuR6o;DGpvq@m$)oMZ`MTf0;!D~^_Z zCyefDGs#~zDJ7kE5h5u1++=Ri;!!2pa@6LBRRhgjeJyOYq z?|q96z8dphQOM$)3x-{3+kcLVN-(hMz73m#^b$uugQIhe6^v-YJ)+OX{2AJQR1|tw zNt+47+T{)#B&iEgn0|L;42Sh#YlOheNs_#o&Qgz?k%x{tT&#u{rJV0n5 z`^QYwf9co#8w&q+0rEd-&Hs24k{>f^2}o%UbV{nXq9(?DGR_8ph+{H=Lx$gh{}ciQ zCZ3I{nlHzi6<(X8w%i*1p0|_E(gjFr!gTTD4MZ5giOCQ`ZzN-RY+4Q0KL4(T!Ok|( z`*^}+xuevu)?hu@qXGGVI#wt*yobhLzkJMqs=Jqwg*mQI2KTu5wh(?5-)Q39J3CJ>!h* zP6SS?9+}ko*bgH7(_`zt*m0FUraR?t(;!`eBw3gbRv-=Wjmg5`)sE1VpS`u@;9a02ZZ86Kr7_+_3&OL)` z-u{A+4_K6XqupQ2l$Q*h-!gCXl_!oYyXHY3&b9=8g7?OwRbrsQ-oY=Q5a6j6iWc%4^BwLs9x0E;Xb`=C2}Xm$gwfJ;8SpU%-8Ii zZjL@(fdS7mWd78lpeXH^y_YU0`I8E%iHhVD6Y~?fZA)ZY1qDnx8;n{G)qGFG(a3@$ zA%3$=1#CfzSWSDj&ns1S$_&9$A+=v)GZzC`dWLv@14&EImEWeL?wKmCs+{6A!Emkf z$Q}4B|EDHi%`w8UH~ui%^U>a~`F$iky=#VV>qW;xN7y$0b)reZyVCxeO!xW>GiaWa za7x0*-7;dywAE(~9&iqbX4tQ1Un!qEQn(&WtG?srlyFt>DEc3M6}aJVqF0?SMe^w2 z4a~-<=n#+fXf#NN-|Tki`~i}E(wI4F%xr8x`*GEWpEM#TuU8CBy}ur+rb$2<`^@xu z9Mc?b`)b)$AnCo5MaLvgz9P4eW@U$jWkT9BjQbqEa-w?wQo^;-8tAD<2m%dTvORgD z+ogel6I5ac|Cap#ny7nH8+W|+^-D2xXg@+8Hw>)ipx-KIrC(P0Xi{)7lc9}2fe15q zHrF<5KGIsN5b=r0L_~6X6^QqwHXpO-@^{zQ* z7bCIfZ`ar%OE_-?V9R-b>>~1CD!2aUl>7_G(w4mXI@W+DIsC>tmj6zf!z0@v08?Q7 zj9x_<-?WXWl#O@smV)BJd~gL|XEF_^MVkCtQ8&kb3$f${s$kc3jkLKP9V;7eK)PVR zpdg`q<)e_Dl_@|wS=KDU3jEaB7)L%QiU?sRF)Lt%j}y$X6RSLob%MhE74uz>;K2} z;-6BF^7an8|6m>MfXOp}P@O9^p#c{H2%j|3r-+Ii?xTm7jZg@^4S5a~ORriAr9Iy1 zfcnUm)irNs+Eq8sR-`RQb5t>rg-PMAea!lK#)Yla1aas4%L~Yd(JH17;L2jXvy7C5 z+i2c%pLfSxL~)}>FAzkK{iML5kkAU*&rSSQ4eE8bMnq(dOR6hX$DVPhiPOnz!6iCp z7)@8pdY}L;`el8wi=nAlEd^P>egX1@IGiI#EwPY?qCrxuQ1kgwbCyz2h1uLdzKF^h ztLFl-GpCU5pMZYy>4259HK-V7IVZ5hjKw>)TD`-$Y(ZgJ<}v!E!&c%eX!WdQRVwmq zB&{^jV)*`@a(P&{d7v&QSeZkANb&L5K(f+0Qi0jl1ow8OSseQ(u6^oKVb_i!t_}2r zIamBg`ZL$J4-8ZM{F>kT^pnaUFusHh6u}+kdUH1O>i{wMP%M>5d|gU>Po~QVX)c&y zcUDLABn3;eFWUGyq=woI+iXm6kw&joHwDZFB=6IBoTt*wJu^vNpANt8s_xe$cfOin zJ&t|1CBWc%N?SexmEP5~-e>VnBY(1DfzoIpy1jYb+9Y{mYDNl*DYD3t!$3K$W{9Tc4r)w+8W)@i)<%t{_@H~o zVIMJt$&ep=gd;#(@n%xPyNW8AJ~#43H0tol?U(@%2256?o-Wvg^`1>lf077*aN4TS zN+C;vc0p?6*vq#yUzm;wN)V_c%prn1gBokdT|f6?g0$m0b9Za(r(WCe^^a-@)5{!8 zGN(MRm~2HqWg!T9*eYrj&xxDzC&t!_{Qo3D3Wx4gS*Yzxl$Jsbp znFXNK?YIMm*_BzCCGiJW4`WtFl;1mFp1yRkw^itVl-A>jyuu$4=QEHt7&${gPEqsV zo1-TXRsK;V;hB~Ot^Xzp#Yh3eW=LwUwKsC;kJ_wf0G`U}z_w76iWNk#%!a%ieM)+w zPi)6WHNMnXSEXco<|(Y7yH+HtOR`%%tU&sWfqnqjh&NuRpZ2s&26G(nTMOxR+z}E5a9o^#P z{wgtcQfpZ?fb=&-yvm)rR)kL^?n4QpAtLS*lBD&>tU*APGaNp3yt7I!BINodC?8-S zg@Bh-);dpPx4TGon;47C-0T28((4_B%9vy2xRdQ=z_BQ6Qg)ceo12l3w|4?d&z_17 zRzxJ84Gjeh25rDF6!QcV#@q--Fu8AX*D{IkAS|;=DB47aEAeHC@q5gkxgNiDryO^q z%eb7_q2)r3jiwzr=r^2?;5XiTwHgd&OlI`kk})&+j1Wy26Z!14x!&I26}za%$%6DG zAmlevkX{CN!k-=-93ntYfT95eRyhhrVe_5)+lBjDb+)uJ#XSFIII;(TtTq+Ss1=&C(uw7z38D!ukL6GD={{r&%|YGD*g016z4l1 zf4+}j3hOtfN>T*KB1Oy(yiM9Rj3)P%Zmzxo6*>ivkW65H!ZcF{u8jD++ogM+((7Z; zi?0gO3#oaQy!Z@tNKwN4+F@Y$u3mWs8t61`Q^^QAc~12*KK+TpMaCM_+Mr)*eYNA$ ztD4|*NlsE%PT-gzt7=c~TIhb;O*N5GJoNM807vD+4fF-dFMk3D$pkh8fXe9~p(61I zs7PA<5~KdJq(xBAT*t)rpWgqkYwYrv-;$Pq&EdfG&-~(EA1p{gAPA{?_;!^1qWB0X z`P4p!c&!p^jwns6)TDPs@`<(`Cy{07c(VaYw=3&`hM^@?XR!Vn_bbr4}OrOnV9fdP0a#c}Yz-<5f^ zqz}oVm0G{ja4OqVdtl~a#}vX}e?f5$th!b;dB;^?i?1&9c#NppcnO|laB52sCKX3O zP&!JFc}xgwYpHMqYS-`mrjkONmLD zZK^lQ`^5-Toz@V^3>sazwp#M&C6m||X57XS_cptAlRF03a@@Cn`KIX31>?Nu^hZ5- zxjz!e3UVYzcF&F0L8RMIDFSBm>9Bqymp7NKj92eM3L(xep_-AZmpsdOhQuzz6@LYF z%JJEN5#q+-oEyU3Kpnn(>JjCHEh*)@4uDnVzeIoke%1IV5A=6A z)L*Rcf6@tm6S2#-N&x{TwEJ?{Yk+LFWaS_!H{n&w>FF`T%R)l=uYbse)^Qr52k24E z%NUV80khfLYIn?X{bNr(($ehiHtYO_pn*HImTx>yko+fMVj9_Lx;)x5vZ-MHA>B2-1BOfr9XRi&xVC$ z+}OE)pWkE?*}s)V2WXI=9ZIkEf?bTmP#5o+7H?L-h

Cq2*HKu1!esMwx`2c55i`!ML0O0{vwB!?=A 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 + + + 4.0.0 + + com.actionbarsherlock + library + ActionBarSherlock + apklib + + + com.actionbarsherlock + parent + 4.0.0 + ../pom.xml + + + + + com.google.android + android + provided + + + com.google.android + support-v4 + + + + com.pivotallabs + robolectric + test + + + junit + junit + test + + + + + src + test + + + + com.jayway.maven.plugins.android.generation2 + android-maven-plugin + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + + com.google.code.maven-replacer-plugin + maven-replacer-plugin + 1.4.0 + + + process-sources + + replace + + + + + false + target/generated-sources/r/com/actionbarsherlock/R.java + target/generated-sources/r/com/actionbarsherlock/R.java + false + static final int + static int + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../checkstyle.xml + + + + verify + + checkstyle + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.7 + + + package + + attach-artifact + + + + + jar + ${project.build.directory}/${project.build.finalName}.jar + + + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + com.google.code.maven-replacer-plugin + maven-replacer-plugin + [1.4.1,) + + replace + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/project.properties b/com_actionbarsherlock/project.properties new file mode 100644 index 000000000..5ca7d6247 --- /dev/null +++ b/com_actionbarsherlock/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library=true +# Project target. +target=android-14 diff --git a/com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_dark.xml b/com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_dark.xml new file mode 100644 index 000000000..ea7459aaf --- /dev/null +++ b/com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_light.xml b/com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_light.xml new file mode 100644 index 000000000..0edb33b4b --- /dev/null +++ b/com_actionbarsherlock/res/color/abs__primary_text_disable_only_holo_light.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/com_actionbarsherlock/res/color/abs__primary_text_holo_dark.xml b/com_actionbarsherlock/res/color/abs__primary_text_holo_dark.xml new file mode 100644 index 000000000..2bcfd0b63 --- /dev/null +++ b/com_actionbarsherlock/res/color/abs__primary_text_holo_dark.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/color/abs__primary_text_holo_light.xml b/com_actionbarsherlock/res/color/abs__primary_text_holo_light.xml new file mode 100644 index 000000000..198384fed --- /dev/null +++ b/com_actionbarsherlock/res/color/abs__primary_text_holo_light.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..769463b369a5185ba2d2fdf26abf058086ebcd08 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^S!aZFaLn02py|Iz^fCC51!MDwC ze!c%VXGhEQ)_dJsezHtpNMD|7Dac@a*_ZJyulFV2w{5C|YCbaz5)ZX-3ak0tIJ#lm tp_aeGY*)k&iW*FhzahTiJD1r}=BlLiI{(TJ=>e@^@O1TaS?83{1OUpzopr0A6)2vH$=8 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_light_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_solid_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..73050476e77aa798919b829a5566973e231f9d49 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^S!aZFaLn02py|Iz^fCC51!MFRR zxpqaJ>-4UOe6iPKwm$=BLD{Wo!i)yScSSDT-Jo*!N?wFe;-MB!VKtu_20%tEPqwzt q4f{lgTEQ5`;-9UxjMeKCf^DQ_uhd?;-Btm#g2B_(&t;ucLK6V6dokDm literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..712a551ece87b2544433ac982382a087e7f1731d GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^S{5)M8Ln02py}Xf^L4oIp!}}xu zHrx6hVG;aws8xB@i!KMDZA_cCWy>wsRQS4KRST!En$HY_#6vK~{lt8cLjun}a%VT6 c)z9eScC>M27UGHi05qAw)78&qol`;+0QSr+Bme*a literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_bottom_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3b9438b16543294498ba27e51d4e878c8ead5a GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^Sd_7$pLn02py}Xh4fCCS+;oB*H z(}QQZt5vXQMa=PTZk@YrXXvE3+ofW5$)(cU#3WF_jrSY!c@8l>`*HAE!gKCvr?`99 W=bm|#aNiDSFoUP7pUXO@geCw#94y)Z literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..cbbaec588ec98bbc8a518a9ab5a9c469482341ba GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SB0XIkLn02py}Xe3KmgB?fcJu8 z^Zp*+-L#E6MBZg;W9F9wttxZ4PHc?!oG~+pYe(tbxxSpjYCbaz5)ZY&8G8*Bq7ylO npZ&2~Yqrog$!|3Wm+fFU5UE)t_U_Fapd}2Ru6{1-oD!M-^V;Vpy{TG>rs2jRbt>pd}2Ru6{1-oD!M|WpWjh#Ji#?9Gi^wblTEgJz>gTe~DWM4fZg4bX literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1e39572224b24a81ed4d73923280ba2724dbaf6e GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^Sf;?RuLn02py|Iy(L4oIp!})_Q zZ}wlfoYKNk`|wbZR*CB+2cd0Do4#G+S+1#YsD)El&1Z%|BAjt`!_S2)TNKYc{m-!5 f_v_(jT(cfAPEEXE)c4^Y$T|j3S3j3^P6 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_stacked_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a16db853e94af78c0739d9b89b578e2a8021c856 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^Sd^}woLn02py|j?`fP(zopr08IHUH2?qr literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0eff695d82911a73874d871f3a7b23b71dd8ab44 GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^Sl001;Ln02py?l}LfC5j;!{`N< zEvnsL$t-6rWU%$psDGg1|4DLDj`q~RzPqNgR|nl=*Rt6dx_ol~p-I||JQ4;82O1ce y*`SQyBHQ|!3>bf({j~je;Zla%ZD->HG+$+yO5M3zoXrBXjlt8^&t;ucLK6T4R5w)s literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ab_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ab_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..219b170fa67aa2ef8e0b11ebff90c1629ba7e97a GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SB0OCjLn02py?l|AL4l*?;q8?- zrB_<6lUu~-<@8GYocm9fskyGHQx2!G9uAwU_k&&MlS%_4GaHYDLBatTBRp}nY76HL mkjg^D5fnx&0glSHJj>3%>1Jqp^q#PeS;bnL@oAJ;NJ=s*Cb_P#ZKbLh* G2~7YBfgFPX literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__btn_cab_done_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..66adffed632f0f6267afe6dc2f518adb6a83ca4a GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Qo!3HFq_#{<Lb literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_bottom_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_bottom_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1d836f65a1fffea301e9cf36770b21b48b3b8132 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SVmw_OLn02py|R(B!9jra;>r_8 z?%b*io|Yn;UGT9};ZTu}!9}%xc^%IgtXD4oJ@0X-F7B%4f|j+c$=0hv54CU#tNF|@ sNQ5wMgi8eI0r?xYZ}@0_Yf6jsMX$B_pXOhT0oudh>FVdQ&MBb@0DzD;x&QzG literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_bottom_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_bottom_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5818666d4e64b93da73bc3d6dc2764bcb500359c GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SB0OCjLn02py|R(>fCCT9!M7)Q zv literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_top_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_top_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..564fb34b4308750b6922f320e9e114b080ecd538 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SqC8z3Ln02py}XdO!9jra;!2L@ z<=I|~`ucZlc5hPnsi>TGwM)Is^N?0TPy5{UQpwgqqAoKG5)ZX-3ak0R7;B{1mWZ}( p*lxaUe)ue*Z}vC-G%ee~_}P|!&DqTD7lF1gc)I$ztaD0e0st(KG%NrB literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_top_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__cab_background_top_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..ae21b760fb1ebecac3389164251b0fa14f580f5a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SqC8z3Ln02py>gKAfB^?4dC zy{Qwe+I<(;Zrk3}qFy7o$Hqotreyxi(D>i`gF-*@tj&^T;Xws!Omj@dZ?l#4Y_?*} ivA^-x{8q}NM@;fbHo20HF^oW47(8A5T-G@yGywny6)bE3 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__dialog_full_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__dialog_full_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..79e56f522b2837bd9f579b28f037ad5eafaaee8e GIT binary patch literal 1414 zcmV;11$p|3P)cSnqT^=WQ7*OFqW%ysfqGq3fLYCng$eebY1E zq0h)!qMt>^T{!1KLrdv5=!(bMwnfb7mMF4BgVA@!3k##^X|etG%o8VLfn)&kp1$5* zAHx|3EF9@?)w)=}!OothSF(h}LSVs2n}u~V8rR2gA&m?6GFm9LT5i!5ovh2$`aOLf zDMwpCv(UIN+s(qhT71rU;E=VYR%2(wIy9)U9BqNMU?EvKj~0!zxmxOREjkZzh+iq_n}2*GaEP(@ILD!5 zWBguqUH^M`clY;tz5a@wsI&tR0g7j89WEv$&WS`s3jmAj>+65+@9$R+4-crSDsl+t zoXD~ai^by4tE;Qe>-G9CHN@f2j4rXz#$jzQA^^+B$H$Mix3`1-5a+ZsO@p#5Q53}= z0G4QnFC7vyL_!;@%<@Jv>_k)mSUx>Hz18n=PE3d}%kYsC@*I)N0NNv^lI%s;wg9j! z%knLLgEIoQ+bysY63^&#(^_eOc(NT6QxTdoZI(#S<~ro`ne!tS(gH7T&zUX-UZRk7 zy|5!F77`FowzZg?X(M)KAw}<4SRP51e8qZ>m)V;$Z3?tS#CiaZ^hhEi?U_=yRh(%c zcGk9kwMUBGYjYt@pHyCSltyExNUHHFevMy@-l#r~QEjQ;(4?Q~r4samHh{FF{bU7StYxgeBAZQ`6E7N`VLYGS_ z;oj>aHT`D+d1$*FJhVBd!G+`uf(ywR1Q(Js2reXN5G)~C&xiARXWFDHmGF81Z6U>K z;#g%)oXa8RP+ENtZO$MZBP0)Pm&4Jv?h_KbHF>VdnKo?a-t;CVB(9^gh389`0#_~3 z>r7%ovdX{d8amFjk%uF>Vp~*9sp-GKg_vAO(?{&AZLdjA|Mdo3?oA8H>)1>mc|==# zB~EQxV&cD40$n%8&w#s-rjOWJ+ddiIQXYxR!^y#)tE&3)24CJ80l~J_MCXPu>Ml3AI+9BHcJ4%0r(Kw z@2dYHj4#nGKH!fqjRVUuh**Aw_CvMrs{b>Lm!YwzO4di>6N@*}Phg5Tw&B1fy2S@v zNaMh=r`Sx3>1SxA8q+hl==5I;NtblfC(XkF^KkH7ZKuDGcq0~@mC!ryi=K7WXgde? zOwz>rq{P&!A!(hIr~D!&pe-W#G7`?LQYh|`P7hzs(O!weT#2|xih3m$l64(P7wFd8 z$(YnkvI-Blh{l8EAha{kf+|%%mTX6hk;YCcRo{6`6g>|oKBz>Tb24JLyVyI4p!izS z!8wv9b%$RIiIZ@y?B&nGjRPyvaF~-xlb-U|=!P~^8?n&FNw@wIvQA!p;xDy1AK0dR zu~CaAl^btMM>A?9->NQ&|AnVmzdt!!WF>xutSl`;$84s2a_=ari#ecl- zo|KSyJXTD=1$2JI1Ql!6el7Y8-dCZ-i%gs^mMBu<;eLzP8a;YIXA&Hxi>7JtAFa#2 UvCQ(L;{X5v07*qoM6N<$g1g$KMgRZ+ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__dialog_full_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__dialog_full_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e029f210b9a81ed4765d31e90b6e49dc8aa37bed GIT binary patch literal 1537 zcmV+c2LAbpP)P*hY@RCILOyI+)4*Y%ioMH6eaT8-v42qXrI z0|4iGEk=d4Xm52^#H?WUz%bBygG}I|;L!cJM*+u%I9T*&&%x@0sx#0Qk0mTmeRX1h$MIxNMY&% zsz{Hz01C^N@8p~dFgXbIRub%{CUOKKzAQ3rfl0r3M&j^DZL~BnC0R+zNTfH*0>l>% z6xe!^4oO*vg0sbY%(i4Znw@-;nUbs&1zN~PIe02zy)Qcz&8uA?+OU=`?e2Zs5}uJN0M*6C#a{)YX?>^k&iO8v%U{pV&i+9YXUL@sAe1fQs10%V6ikBo z^7Qod_s{$xhgN}Tci-i5`3pX{;N4;1Au(Bm7x@)z#1I9LmdL~RrF4uC5rsHs>tE+w zbjvJ)LKv+{MYbuBrU=TBLj=6Xm=@t7;baF+QjNr0K02$09MPPz_Q^dZ5CLh8#Oov4 z0!TsP8|Jz<^->fRi@r;w5OrSCkw6L?N(J|#V;?nA78HxV{Q97DD=gWeOHxKs4U)p3 zSoD>;waz;geHn?ahed6Tq%7rduPgn zV$nDE$&ajtWS5-`4=n>hvFJNE&bxxNGnt2!k)T-g9UO0!_0BFb6cm%bLuC>qYeBK* zyN@VHxuTkNE#cFa@PaJz!! z*)p0eo;YjAyoc}Lcn`@)ql2XA#Le!l%y)3C`#|D`qk*LC#Leoh&{v9irz@IDt(4j|E-ey3YJCdAoj z17Q35`nvale)TKB+I=4%AAxk%w!OMGAf++t+Mb@Cemp-vW4j&J%V|A%#TR&cd&9%S z1Mctdf1tZ-6W>Id5JKwGXhvG!+}wOSKR^Gos;Zyq!S}5X*>+05G4|7V9G{hQ?!VjH z+iw8YkTX)YMrymYx~{`?w$U_yh$q-O1#m*zu<(hQDx}lJ70s)V5Io{ZN7v2rMwVC7 zhTm_2@1*&pTK&uP8_~o&lye*5+om^+1{h zqafFH?O0X{OUO1M^YAPb=F`bd6<0LB=s!LE=ilN39Gach?DYt$CL6UhXTc;$2zpT2dqqQrIZWT*_yH->_|IcgE|Y&S_AJ4lX{3 zlIkT6RWeyS^JtZ1q^3@yK=Cm?J&*-y*C;dpYgTSF&bztX%>V7a(kL+ra#JUT-|=or zs-McUNbE?uO3e#!@v#wf-Fu%ryG^`~69lNs`%MmnTRbrM$471B$+eE{K4hK$mCQ9h-Z$E`tJc<=E)8`p?U8s&`700000NkvXXu0mjfb2aWW literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ic_ab_back_holo_dark.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ic_ab_back_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..897a1c11a06923f0ee630a3ec44b40118c1fa4d3 GIT binary patch literal 602 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1rX+877l!}s{b%+Ad7K3vkswhI zFm^kcZ3hx8D{xE)(qO#|6+TOsF)%PDdAc};Se)*?;+-wxD8l++z18QgepiI9o*=R4AA9|r|3}>uTm;M6YILUxmVi!uBjgaN)~;(wEWpmNk!cW z(d%Z<{E>P~YK!lT=8FgRsYuTJwQ$0$u+I~@?rdLb^6_f+5jpHV#tRQqPS1ijb30F2_W#pYUFO#_XV=?h zOHP?#2^DqDa literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ic_ab_back_holo_light.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ic_ab_back_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..0c89f71407e8d51f92ff6a10b1ae40ec902aa04e GIT binary patch literal 546 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1rX+877l!}s{b%+Ad7K3vkswhI zFm^kcZ3hx8D{xE)(qO#|6+TOsF)%P{dAc};Se%}E<)W9gqsXz3_j5LNPFu1pO1DK} z+N#=yeg8w_ofMzAMJao1SUGXl-8U7Joy@GYEX@zknfd?wSxaLcYqyHhr&soQ-EO{p zw!~UfNcQJK)l+|B(yvWaZRmKvsC8=g<+E=!6%t;!XnxPxBUZuOVKlwt*0FipR-Y02 zmCh$xWIQ?dubRMH%k4r1TjlOEFztJ(`Lu4f+1J+&0<|T}CPeP?x_?Pcpm5>hNwa5M zzkQR1sjpY$)<2WmGuJ)*zvffcmaNUK`~10Vr~dw8eVc_z%*|+OqxE^sCGQ0OTrOO6 z<>J@Zf0Ym33CMeTp!xW+<7NU=B(%1ExnpuV-^$?oQTEn-hjMN&=46?A$U{|X$JUzr zjr;q)=$3Bvc`c`@@Ze;4?7{TSRWGgUnXHd<-`o)9JVUa&78r!8C9V-ADTyViR>?)F zK#IZ0z|c_Fz+BhBFvQT<%Fw{d$P~!6GB7Z{-*6d4LvDUbW?Cg~4NgrK`9KYlARB`7 r(@M${i&7aJQ}UBi6+Ckj(^G>|6H_V+Po~-c6)||a`njxgN@xNAF&x-_ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ic_cab_done_holo_dark.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ic_cab_done_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d8662e3f0fdae62cdee68c184a30fa9e421dc338 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877l!}s{b%+Ad7K3vk;OpT z1B~5HX4`=T%L*LRfizez!?|}o;S3Cn7d>4ZLn02pop#z!+EApeUwz>goe2TrwSq2J zirU3kvaXhOUTIG}?do{Paly7dd_m%&JI?q_-onB2%KO8{j^-Q>wzNlYp6R5&dH;Rm z|KbvtPbZZ7*R_9QUBqkhp({MdL+G`PeiKVkcC&4Y!{nAd6Z!S-oqj3U!mzMkIqyf? zf)_G(nEp@p{`aZ-I!9g5h7aq1927O&FPT&GvG@NuQA5$TGve!iOqW^IxiY{>!{pzO z!{3V<)Sk6$zkBfg;!caFvF0E2Z#WAV9{aHQL)vPusEmizHw=GVH2J}B^#kKc_ZNxM zzDF#RGe0;TaIo&I-m~b0*sP8To$2@Yde+QKc8kg_`XHU5q@;HFk@ug+O{X5aZ~nk~ zjknIKwfM~0Z^b864VUMM@zwGAdYt)v<(Nz4IlImB@8#qJ%zn(A@Z29bd}4MpKn$sTM506307QmPCTFYr(@#O zpZ-(V%YE!?30QE(DXivDMdI0PFM+AKOT1>hnX-6tX;O6(d#2d)iSBZ_kN7m7WzTRr zzEA`h)2bz|5hW>!C8<`)MX5lF!N|bSP}jgh*T6i)(9Fuf$jaCl$hI;t2>JhQGm3`X z{FKbJN)!#IR;K1a1kqq?mJtlpAPKS|I6tkVJh3R1!7(L2DOJHUH!(dmC^a#qvhZZ8 Q4Nwt-r>mdKI;Vst0K3d3fB*mh literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ic_cab_done_holo_light.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ic_cab_done_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..ed03f620f8ef9e969d0471ab76329038e25c9f0d GIT binary patch literal 737 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877l!}s{b%+Ad7K3vk;OpT z1B~5HX4`=T%L*LRfizez!?|}o;S3CnFFjoxLn02py%z6(IY8q0$M=`{k_{&Q=;t<2 zt8!*r>&D`|W2$+>pS=eAb}M##+^cbdA4dCLPr0$oL~Mm8Bn#4$+9@L$(#*Pp%fW0&@t4F~+Q4$6OeIrG`|nG22@uVa-C zJ=H0n)NABBE9V2ZrDh$}Ob?>#U7^{{%b z^;_|*S@)*|RLscvyw85$_Y2}8p?L-UY)vO;|NFUQJ(sNEA?BESpVrme#d7mboB7l} z-Tl+e|M{(6F|!lpnSU$jtXi?CXvKcs<;$n_y<4FKOaZDTt`Q|Ei6yC4$wjF^iowXh z&`{UFLf61N#L&#jz{twj7|6CVFbMhoZ8M67-29Zxv`Q2WrdForKm^fXYnBlV)F276 tAviy+q&%@GmBBG3KPgqgGdD3kH7GSPrLyp3str&PgQu&X%Q~loCIB{EJ2wCT literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2abc45809c62513224e9d695542cb8dd8e8087b9 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezt=sPZ!6KjC*gdALL|E;9+*q=6m<7 zlueVX_4IZHnJ0qE#_rG0T(kiyWMEjjhCTn?qu5pu`=4q}27?c$C=-xvpo(CHaDZeV Zn6Yoz!5==w*?y}vd$@?2>|OKBsl;8 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_light.png b/com_actionbarsherlock/res/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6aef1d069a14a7fc1cea9780c919c61679e4fa GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtXipc%kc@k8uWjUIP~c#3xZ62X z=G4p^oaPsfxfS0Jish)R3f(&WG9yqa14Evg(y_dGGlfCy2iXqR3?>_SEs{VyhV?DH iJ0OaDc|iOR772{!kNsXaOPT8&i0|p@=d#Wzp$Py$I4IEo literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_divider_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_divider_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..986ab0b9746301f2dd9401829da09e00995621b3 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Asp9}6B-)+^BAxRtXRm= az`@Yw&#rLZUbzUUfWgz%&t;ucLK6T(%Mo}0 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_divider_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_divider_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0279e17a123f8cbb3c7e3a9ce5c5af8e693b6977 GIT binary patch literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~!k#XUAsp9}6B-)+^LX%RmN2q0 Ycy4A9FVZ~13zTN?boFyt=akR{01+Y(GXMYp literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_focused_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_focused_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..516f5c7399c853d112a31d1e17c8c7f17180f9bd GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEX7WqAsj$Z!;#Vf2?p zbb>IW`N`93fr5^nE{-7*QpO1)z4*}Q$iB}R!lH3 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_longpressed_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_longpressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..4ea7afa00e2bfe057472ed5a196080fc80ad7383 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEX7WqAsj$Z!;#Vf2?p zbb>IW`N`93fr2)kE{-7*QBRJJkl{&7FVVepxUS$Rt1LT2VAvn7lLLt0oo!v4%( tpjG6n=4tr&fORvYRP%q%M>EqU82;Q9of*be(FHVz!PC{xWt~$(699PnDoy|Z literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5654cd69429fd0a3502a05b5f827bffab89cc7e0 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEX7WqAsj$Z!;#Vf2?p zbb>IW`N`93fr5^nE{-7*QD_LZ%D`aoRl?HIGCc%n7=x#)pUXO@geCyJM=ZAh literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5654cd69429fd0a3502a05b5f827bffab89cc7e0 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEX7WqAsj$Z!;#Vf2?p zbb>IW`N`93fr5^nE{-7*QD_LZ%D`aoRl?HIGCc%n7=x#)pUXO@geCyJM=ZAh literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__list_selector_disabled_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__list_selector_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..f6fd30dcdc9c39c836e509486854f9da03486892 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^;y~=k!3HF){@Qy1DVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s(?cuyC{kcif|*A4j^95`Gq9-KV8B6o6ra`I!Z80pW;7d5zZGhRBg zm{FnhT6Xzih3ZMUGkfBc^kAT_eS7{3zPQ8d#DAm9s(?cuyC{kcif|*Eb3_81S$-+V)J@yPBVWyvqG&GeYz~KYxJdw0eeyIoHJnOEfn# R{sUUY;OXk;vd$@?2>|r{LWckV literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__menu_dropdown_panel_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__menu_dropdown_panel_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3d208578c61662986fdc16bd15c69759b48d6a GIT binary patch literal 922 zcmV;L17-Y)P)!ytdud6iMT7O&4bD*mP3eKws)UN7D2k#e z%DbYt@4NEYnkjlINEK7=*RV3zwml#Px9r!q%}Y$QM*NqHEZ-wscn^ zf4p9=Kg#ZZAKY%YZvc!aCbL}F6eGxtBSzy=smEUmc6C{?q1YCQrid~5o$!73+BeU$Ee{DI^Yo4#-o87 zao*xiE9Z<+s}ttlWw19HGlEA1nW09+*~|#}a;CkeJh%bU1g9CP5h0^O2}4Hs%y=Lc z5q!OY8j*^uk~rBBK?ljmh#jL84Ev;rDo>_H#K|6e%Mo=CgLzw$rI$Y4Z-kUy6U47Z zMI2w^tmuV~pH%tJq!^(yWY-hbFl2;G2g)+VPqud2Sicc+jL>MvBTKK;HbN#jlrS<8 zl+;FqdPULzJ~Kh_!?j8>!xs^*nQAm$#290~ue;BBnY1w&wMw4#m(pwEDZd_oY1{Ux z>$>N)H(eWD*FCpw`-IsDKF28-6~1bz!JGs-W6Z&R1n>#KR{&q8us99Q}8ID#(NJr3oa@N-D4Rc$qhA--bT2} z_Wpf@Z`4749}#V+fwWJz^oyZKO1~J&exY-1*Kg?D$hu!fixvAiNfpmG;mo&f5BSEA wlE;@gYed_N;JFm#Y)Zw{1W}kQU9GkF3xsHEK4Myn7XSbN07*qoM6N<$g18=|djJ3c literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__menu_dropdown_panel_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__menu_dropdown_panel_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..924a99d173082ba58ca7527822359f228bb14dec GIT binary patch literal 1061 zcmV+=1ls$FP)#d)-&;4-At916hopygyW1WQa|lkCWZ4-q&QLYwjy>%Jd>?UvK6*fy zdt!`Ep~K-gOUxeEG;B7TCA&L&Y`5FR`f3tF5S^VH!lJsKJqkfK0&PN7nPcRS?`4Ev z7V~vPKouy~L@5%v*=&-(hy0cgM*ETwjWz&8F3NVhH7#V4$3+kVgi2lPw-adOTXm|2 z;f`P0rx+seIw-H@dwPaOx@-q(hY$i2BS4u1XM~+%LI5%mN(D;0XOsl63U@~43e6EU z5mrW96C~;L2O}H%p$jmGc5)7LM&^oyAacV=14{s_wp`K^YIpyDFEM02mXk3OSEw-p zdORS~SGTvf|Cjl{`o#71_3u-2KzcK(ZU7La5JAil5&>9olp#5yVJhG_L?OnQ2x7(v zIfg2+Q2z$cZ7gkJK~)>%8)`3CJSNBlBGKwWz;6QnLDPCN+(q9 zAJ*09wM4d_hZLYxWa z2;mvCbu@F-B1H21)loz{?U-+?@nqk8kw`Vm31QkJFg_V%{OMfZr8mlq(-<|uJGVoHpVClAlh&-bsduQ(hI zi(GsU`1tt1hf_X{d`3<68b|S{tXYwd2YXzXl8Izg4y^21 zp<@H92ON{kB3P1HizBE$;EP~xzks74y{^`3Ur7(FmPO0@t2+WXW`VN|Gc9|AJ43ds z=_5ZJGT6$YN8bIa6HYZ^jFIn`&>W#9La9l$?6@3`ET&WsUkjFK0@?|yO)(6`*1l_s~_L1z$~& z@jZM)qqe)$B+Pe}79nG2sji9uR0#8Z){~%#a(p4yPG;@1CipK8G=YCTO^$u*mj`Me f=rGa5Ym5<}3Pv>t$oVA_00000NkvXXu0mjf+qu}} literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__progress_bg_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__progress_bg_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..310c368e7a68479307866c479d1e4eefcf5db311 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^96&6^!3HF|1ZAax6icy_X9x!n)NrJ90QsB+9+AZi z4BVX{%xHe{^je@`h^LEVh{nXLlN`Ak3Qu?vqkba5WyT9CKZ;N%wBhP~?hKK2LK3q{Q@b8ror6q0>Kp=4vJYD@<);T3K0RYm>I5PkM literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__progress_bg_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__progress_bg_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..70cb7fc7e0bcfb850d4b365f1bccb5b743913e21 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^96&6^!3HF|1ZAax6icy_X9x!n)NrJ90QsB+9+AZi z4BVX{%xHe{^je@`fTxRNh{nXLlN`Ak40u@7AASF0zb~yy)74d)-}Q|XM+&=H%L1l~ z70X`DZ5Q8J;=>T6aJiv_zhUdQx9rw$B!4Wi^!v;8!uD-LM?h!CZ3$B@rZO?tz^Iy& QK(iSCDOJO>Ym5d24oA2hD9A9kkZ#C-ocYxQulY$%dG^s1`jD zWrBMf{hI5z?Ps6d&cAMwxpu9tAQSLC;sV+l?CI!atlqGv_K~X}S&7D6#irA$G%zcr zGa^}SvXODrvrsWB#ZkB-c}-Y}>Q0!#L#9IQCX$ZI#7DnF9fJ8(R=8iR{NJvIR3GfdBS0fGXvSYugM@B@Fy~s_l`Jy)|ccqHxmVzApHz{x4N{y z#Xj0V$LH=@zsWY)$4Z)6--edG^VYGh&b$}$F3XeLt5=Uu0`p37@FaypB&t$KuAm9R zq*|yt7WOdr(m%g{dU1bs0-qy(nh~3JDc<<|K^(;cR4G!8UrKd|p;N zi5zZftW#Kk#vM}fdS+hLQ#;Dq?H?_`8P1nhqmR=j>Au${MMR)hh&5HCA z?cjx6pHV1ZFFx`ra1m|YMALE$u3RAxZp(6Eus@DJwzn!G7Cw2Mm%K&AN5Ia`DuuPd r`dvHM#yxB2nYDo;0!e2cs)*3P7gB8$K+RT%00000NkvXXu0mjfr&_ZI literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__progress_primary_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__progress_primary_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1c269205e874bc6addc308efe5be4fb7c5da0edc GIT binary patch literal 917 zcmV;G18V$CDOJO>Ym5d24oA2hD9A9kkZ#C-ocYxQulY$%dG^s1`jD zWrBMf{hI5z?Ps6d&cAMwxpu9tAQSLC;sV+l?CI!atlqGv_K~X}S&7D6#irA$G%zcr zGa^}SvXODrvrsWB#ZkB-c}-Y}>Q0!#L#9IQCX$ZI#7DnF9fJ8(R=8iR{NJvIR3GfdBS0fGXvSYugM@B@Fy~s_l`Jy)|ccqHxmVzApHz{x4N{y z#Xj0V$LH=@zsWY)$4Z)6--edG^VYGh&b$}$F3XeLt5=Uu0`p37@FaypB&t$KuAm9R zq*|yt7WOdr(m%g{dU1bs0-qy(nh~3JDc<<|K^(;cR4G!8UrKd|p;N zi5zZftW#Kk#vM}fdS+hLQ#;Dq?H?_`8P1nhqmR=j>Au${MMR)hh&5HCA z?cjx6pHV1ZFFx`ra1m|YMALE$u3RAxZp(6Eus@DJwzn!G7Cw2Mm%K&AN5Ia`DuuPd r`dvHM#yxB2nYDo;0!e2cs)*3P7gB8$K+RT%00000NkvXXu0mjfr&_ZI literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__progress_secondary_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__progress_secondary_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..40d0d1645cbf05e30bf092ace45403281da7f318 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^96&6^!3HF|1ZAax6icy_X9x!n)NrJ90QsB+9+AZi z4BVX{%xHe{^je@`oTrOph{nXLlMZq=7znVWA2oV&$ZSjW?F|h7ziq5jS#e}ezd(V) z{M(JTeF@SBrcbxn{QE=WZYK^04VRD=846z*9&b9ovtVvpnl(eT%D#{N!SzC3^S3#* f<|u1MCr7h0-;i__`(soEw28sf)z4*}Q$iB}%FaDT literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__progress_secondary_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__progress_secondary_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..40d0d1645cbf05e30bf092ace45403281da7f318 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^96&6^!3HF|1ZAax6icy_X9x!n)NrJ90QsB+9+AZi z4BVX{%xHe{^je@`oTrOph{nXLlMZq=7znVWA2oV&$ZSjW?F|h7ziq5jS#e}ezd(V) z{M(JTeF@SBrcbxn{QE=WZYK^04VRD=846z*9&b9ovtVvpnl(eT%D#{N!SzC3^S3#* f<|u1MCr7h0-;i__`(soEw28sf)z4*}Q$iB}%FaDT literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_48_inner_holo.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_48_inner_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..c8358e9cefce502030416e05dc8faff139b886b2 GIT binary patch literal 2081 zcmV++2;TRJP)YIdx5%v}IY-E={))x>B}kD^;1aO=v<01PBB|;(|LQ1lm?k zLWq;Nn-CY=ae0(~!^>I`t6s zs-B%JpquWw-vVT&b%#DKYKZiAR|9AYVtSC>+D1~E4^t2*_YE}4C7t2scS_b&i9ji7 zBYSvMwdlT0Go+O!I1;=rq$n|BisnJx=53v0;@hqm(7l)*=4rJMo!clNrP~Z@m{E-} z^JZ{ZNHx@{flX?mQSo``X|C|5u79fpM_&?X60Kt|A5rbxJ7+XZpRQ<_6xsZ!_#wo! zj#h1_U5!M-Io2>ob>UEYg+LRijsxnVI$Sub8=TigMynU~iXTIjns`*ZXjCM8l?je< z^3e1OfW}cB&*(9#LY#4(p+|S(CFYo)RH;RK=~N=zD5YZ@e|b@)#etF>)Z-yez@UzD zxn@bF&zGN~8oIPk4dfy-rEX5Ww8$EZ0F70%U;9F1W%-Z($&Ga-Maup1vncKQ7_DKl zn9!@7d$FjFqs1PKM)U|Dr6$Db)oTnV%fNxx{Okxr+DM%Qs^$R=K0p5VBBLXP|Go*uWAqrP!s#9EjG3Y5H)_%&0w$xIP8)2<gS?toB)4w_5VP~5v52jeNf?a@l%z&Ft# z7nB8|dUgD~fFBhAa+9}!Fnz66uJJUZMq$&Z-=x+YQJfJpw%hO;S zbE}u#&MHNeP>Upl#AP)>l0hdeKfNO4(#tQm-Akeu&02@RbFR7XY>6_k1%f(YAT(#fsAW zCKBlg4QBOIpq?mn>zJh1X=9x9hp4F}kQ(@481j?!sz3o)0BA?xOCB;{@vwLHR_qA* zZA`n>5_V~?==zrnj46C`9p$WNjB&zGc+-W7;QUT(*KSn_p>ds2!NyfVppL*7{NFHR zx3J%}=U0sLd(^D`)P?PxcXaJb1;-WXet3d^dmIcA@AOk%p(p(THF8j!=km|#V)bYd zpicX$PljfTc@O)})|@FD=Z~mC2i2imPj;N6y7k2(V~h2N*p=p#-z6}HxcB*hYkSJV z`IFSdb9yY-P}dA6b@>ZL?=z9&3we?^d^OD4aqs1Mx13&f#Qiy{cDn?wohTPSPjL-WpW1m}O&Ju5YPHyotYlm=9`GcY zrwYvY8;-f&ZSM33oCpsge2-Ip%afm3V!sl6q6gM{kN3J3%|Xrhwg)`uVUIiGY!FzE zIO3Qa-0aQX;)a}NjB&uQ|K;9KmvHqg(MeCoc*sw<&DEj$2|GyBp7MmJgBi=!jyvfZ z$6Ymtf^mwAKHp@Hd-~&L-Nsi~>l_hPjymE9wjDU*%(E+7 za!uA`P2O+vKSpg~;?JJlUH||9C3HntbYx+4WjbSWWnpw>05UK!F)c7SEiyAyF*Z6g zGdeOeEigAaFfi_TCsF_a03~!qSaf7zbY(hiZ)9m^c>ppnF)=MLI4v?WR53O>Gc!6e zGc7PTIxsMwC7)_* literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_48_outer_holo.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_48_outer_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..f62f74bb38e8818fd970b7ec1f7862e543cc9d07 GIT binary patch literal 1811 zcmV+u2kiKXP)Q(x@RlU0R z*6*F+o^#JDHf2*b#j2Gf_PMBSbuGRn) zDJUkGcinCw!4`)d@_D~EMJnC5dvGXBz z^GjW6Gg4BPB{MBwxqQWN<0fP#?6J=Sj@WNJ!AH@8Kl`>9L$xnJF(@mqT0WPnt1qF1 z=Fx+;deGw@bGK5OQ+L*P{Upq+6DX1yGQbK>T~%EkI?XGa@}$o>ppa%>^8?Sie*2l7 zfHK3%D>%7|N++0Avr7*8vd@}G^Jo0fcfyUHfnsLZ&jU?a6Rn7I58e-AIzUHA66jy!AvGyBgcAIA_8&ya%2-o5w@+x^Frq z7x5uqjDu?ciVPZ-34&-=b^T>U$i3owu8N|PCmm@W9a)>Z@0e@OLVt>;Q1iTBsK`Y- zJ#Dm0pva)1q@Sv}wFUZZ<(6Yki*muoJ=kjDtmVDYq#tH=k$<|@5vTpkf+%-~CzQJb zDj5=FGBxvks&SW*YJTnCqA2*7ySf013>jGQpITpe;Jn|eigLRhZQhTXHIx~a$pm$C z{nZbIsz13R7nL1tHd-@KF*$Um(og?cC1;!x<)VYeI|B_UB@HdgHw5UWlj@?}cDp+O zMal|-j9w2i9i`?Cb8^v;J)JZZ3@FG_LfsGv{MVdZZorO4w{8Y134%<%0TH$=m)%OW z_@u(SziK>nET6ihG`KOcU?%zJh~m2+ycN)r5;qQq+h%%Xy;f)Ejn0a3UnsuS^+vZqWP#0th z#x}@k!|oI0qU+rNRV>J4GUFR$v|a9z%jMoM-5IE6CMAy{ll?bZ%p5f-7cDwzt|L%b z^86*Y-fp1pM3X+OAj(~JqWJ*PYCpbZCY5MgZSAj)V(>vz$zUg(ZywPKsOqx1Or~s4 ztCPVxWShrQgx&N<^IZW2u9-7hOofKj5BzxpoW= zdO}GqH|+&2+j}j6LUh@cv}2FCzaLvtWZ?^TwHb8;v&L-C`+P^-e$fT!yBz5Da{oL7hj@h;h3%R$P7X_J;dmT2? z8&V8*_<~22M7c%3b3D}BjlNwczv7fZds1ebGU8;svesTmESU0X4=BlHYF_eOt8iaY zL0I~yq8N;M$m4dU73%)xM=srNe&@#dxWo1;CZw8+{^f0pouFbc zV#*`lZz%0)HGlQf<_c&{Ktx7;z}?CzodgvZopY&OUp7)Q;eaFVO?})iuy4L9j zyLs2K^qSz1EvYP7>Ws{|~_-QddT;IiL%she&}m+lY?b~t3u zik_CPT+O0+6?K`iA!CLOr{nnY$OW(ay)*42s|TRe$lC9I+YPJ$b-IG8H~q!myML>= zroa>ruDyrb zV`&jHgSNQCgmI$=lu=hRYuXKOo3_xM&-4jsmA;l<3@^Ph&(-?zXM#=Hludb0$^QZH zs3&Vcm*{c;001R)MObuXVRU6WV{&C-bY%cCFflPLFgPtTGgL7)Ix{mmGBYhOH##sd z?sz9s0000bbVXQnWMOn=I&E)cX=Zr$HaasiIx;gYFgH3dFrOu# z?f?J)8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?002ovPDHLkV1l}_ BPJaLZ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_default_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_default_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..eb28ff9a5516c15667fa8fafbc22d608d1f77a06 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^%0O(y!3HF+1t+BgDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s(?o1QL?ArY-_Z*Jr|WFXM+FkX}IYI~tj_}8!bFJ^a4j((ibs-&WN zGIsXEx)Wtn`lnsEAmpSFI79ccn9RKAsn<`<tk;t}o>yjV{qp%WbL{dki&M)*dw`x~@O1TaS?83{1OTcY Bcf0@q literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_default_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_default_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d281adb553af892f758407b846bf31810b9d776b GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^%0O(y!3HF+1t+BgDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s(?Tb?eCArY-_Z*Jr|WFXM+FkX}IYP%rckLbO*zpbz{y7bYSIrPClD)2Hc3qcaC*%7oYhKR%+5DyWbJ~oU4#{OKNlluqE0VJm zR?Ud&kaA;Q)O4{aB-unUR3g{I@EB82%f_aZWF5r`AFqi`v@HN~5*v&Qo>cRA$gv99 zHaapFv&*%1#4~?bwda`9)~}vNKHF@K`t|cGZ}Xb{RR4x!PC{xWt~$(69DLj BcXj{( literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_disabled_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..b2985860907ac324b509b76731e8ef9e01bcef39 GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^%0O(y!3HF+1t+BgDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s(?E1oWnArY-_Z#r@{8;G<#%>2r*^uo7)-)HXL@_O1Ny%Iq#{YfeF z(mvLmIJH1t&`=}DOJmBWhqv04yB582t6X;JXNKdwfEg3JZl{PztUeIIdQ8h8YDr!LqoiBzejNy&kDcJs%rP689)vmGI{X_;^jtl6gm^<0^v)GdFRa+YOJx z7F>vKtz@b&Rr+MtRJ_Cdn)*bIu9V&{pI@`*?C`z(Yf|Vzp#K;=UHx3vIVCg!08ssU A5dZ)H literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_disabled_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_disabled_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..4215396dd4e51fea9239323d313b72fde0ba86d6 GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^%0O(y!3HF+1t+BgDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s(?E1oWnArY-_Z#r@{8;G<#%>2r*^ujlV;{V25Ur(E)S0c!zKPhEi z+Q+&RrxwTy8fpZ2X-wJl@K&30*P>T$mCG*u%y7IHFk@oZ?G!PI)dwP2k7*f%?N0KT z=P~Pe`QZt37-AUDv?qm~sFi(Y>bsQr3FCw4S+92))lbOG-Y30G>`_~0 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_focused_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_focused_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a280eabf59b5eb69fa2a84280402b63d5e1bb8f0 GIT binary patch literal 524 zcmeAS@N?(olHy`uVBq!ia0vp^%0O(y!3HF+1t+BgDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9q_DjO#pI977^n-`=$MKI|ZI{A2#4tV=npb9cQAXy4X!_bwM}{Q4K( z1sl8bnxc=22fURRmTQmkS`l-5N%{4LzmAq3{WkJ?oSyR(lyW*m>cW(jDj1r7drC_% z+Ahf4U7%?EfVHA#U$%eE)HUhV{* zGqMRv58QeS1t+_UJh{!Nwtjop`q`x&KXzE!mvvsrOU)9V|3+xFYu0w7ttSN+pN66C zV4oMA@AG&3wK%+`$wcPZ<#Ux2T-c9-*heq_yZUm|{V%D1UlxhJm0zYfUETgVr;hpA zt-Fj|Jsx=O$lYh^6WL+vBibSA6WURD#!g`Ij9SHG25Jg!2FnzxlYN}N9h%V=a_mM! z$e|leQ;yweap>IrEpmd};fhV2Zrn5uN|m^)*v*dyQIi{G2ej@g`j zy#1roz10ezmNeBYwbLI^0W9{MT&2dcxOa)`zb@?O?Gu2 z*S413TzCHUdV}>`f)DaiKP_4HkL&Y-WAl>F%y}d1AGY65et#XKh5dqWo7Y|}14bu< Mr>mdKI;Vst0N8Ej(f|Me literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_focused_holo_light.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_focused_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..f8d619b4d47ab5b104d6b1042c27fb16a3beca47 GIT binary patch literal 523 zcmV+m0`&cfP)C3<00004b3#c}2nYxW zdK`GU$CA-E+tg1FUj3CV1Ez}l7r{NB_YWZ zgb+-Kh;2qAgunr)owSNT9jE|#(}iQ{0=K~F)j_f4%AIwfW*LI4SCjSena$3zhl@?`-N)f>fC&00iU>|7tSL}YGdG832Z z7Ry93w=$W@5~IozqskJa$`YfBUCf~8@-tLEe(Yg7ZJ&$d5^vAJVlkbz&)H#>=1rCU z=lI*@D9QucAy4<)W_$YF0~q6}sI5vC!>ie*fX@E1>5KTAKqegRE>ywO`Fw(I}^ N002ovPDHLkV1grX;W+>R literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__spinner_ab_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..955a2f34061ae8ac853050da355f89409fa0a784 GIT binary patch literal 464 zcmV;>0WbcEP)C3<00004b3#c}2nYxW zd0`Pl=$b2!s#>@tS z$Y#JV#)+K6SU+tW#+_RkXYzpRi_e9eTN!5>F{(z4su81V#Hi{OLkTB`etUYuM1<4( zFh4)_STHCiBAh-(+2ze7j|0M$*V@)G5^^0JhFHh^fzV_7Tf?-4Si|T*tYCB@#xY$W zMloF>#xN!zMlhxzoiQdMoiL^$+87%UEsQNlYm7|@05m?}iO1-}bYl(D3Ue3c7gw}A zD?zp;CtICi-;5NDtN#ybjCs$)&yQmArGt<$4!&l7(HXO5p7J23`oDJvMC3<00004b3#c}2nYxW zdPRE!~6H3V|7LaeL+kwzxO}{DvWjdA^uWhB*Ox4MVbeVt$Spwgkx< z!VkuXjKgSuY)h6Hw=%|LL;AyKLdLC(F?ASO9Y$7%k=0>jHH%rr7Kwb5&>UvDaCo}j z=j+357qeVAJX{{~a_138(!I8ROa__X7ee%7enXtUzOE0mAw(ah0z?m{B1Ao=21G5U zCPW=Z0YnW(5u`pwA*3EgF{C<11Ed;86J&FYMhHOp8-||_vny?o%`ne52fWVraQiWC zKdv-wupMF9(yd;3%<<_PN8|TOh%C!a80|#$F{54SO;q)d{~Z)(nS?QFWpYf|szS`q zFpt2hCCEz5&mZk`@i_(t4MLWruXxh=CcXUn1{k8P{&E{)e*gdg07*qoM6N<$f@(ay AcmMzZ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_focused_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_focused_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..673e3bf10d60cc54b6dfef2fcda24575073adf61 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1i!3HFsuehcLq*#ibJVQ8upoSx*1IXtr@Q5sC zVBqcqVMgJWBc+iXw=G=Ra=)z4*}Q$iB}l;b6k literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__tab_selected_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d57df98b501944b4ba63623766c396b5bccc29ee GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1i!3HFsuehcLq*#ibJVQ8upoSx*1IXtr@Q5sC zVBqcqVMg}h~fd(*my85}Sb4q9e0H>EG$N&HU literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_focused_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_focused_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..294991d7931432f4ce0615f83594077185b41e53 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!3HD^Kbl$tDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s&X6Hgb%5RRG22KRnVX>4qKXwM^^Uwi5RN9qg%hl7#%FO7Y8A{hJ} m-AWwAG-}%xl(FUbF)^t86Xr1Vx%dsJpTX1B&t;ucLK6TtwI;Oy literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_holo.9.png b/com_actionbarsherlock/res/drawable-hdpi/abs__tab_unselected_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..19532ab10d4fe414d597ed44ed50c91a3e3b9279 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!3HD^Kbl$tDVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s&XYfl%)5RRG2KmPx>XJ%$TT(5HGK*O1ak257UFsy01aiHFf4G12u u^?&$m-q&fz7T&FR9W0_QnO}Q~fkExE@YIkG=lX%hFnGH9xvX9s&XV^0^y5RRG22KRnVX>4qKXwM^^Uwi5RN2-aD!@msM(PIt=evwJLPDoBk{4*AEnmdKI;Vst0H2gA*Z=?k literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_solid_light_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_solid_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0706c8af658bde9602634950dfe3d5fa5886163f GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%zMd|QAs)xyUfsxfz<`JK;_Z5A z-Y@ApO*&r-OgQM+#9X>wkYiu(tId138dhyGKCm}H$eLN@qysZ=3D*)nlM7YC@0xyy hn}nCMmBhQI$CPR`ipOiMnG7_T!PC{xWt~$(698&aE5rZ* literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d814d02d31183b8f00f475a05c124004983d9eff GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%PM$7~As)w*fBgS%&%9EC|%_%783w8jlt8^&t;ucLK6TeKPwmj literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_bottom_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..b139c8e49168e4404df0a46b30a4b30e90c1ccff GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%PM$7~As)w*fBgS%&%9ECY>s_{MVWOZ(=mjBT1_sGr W&p6(H%v1uJ#^CAd=d#Wzp$P!PC@9VV literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..743d00b6cd7e446c7badca9dd11d1579404569cc GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%KAtX)As)xyUQ!e~V8Fq8aW6aH z`M>?!MSK0VJ5DH+1)MRKpSquM-S5BdOIN+&sn~jE%jQNlsf-1UY_}X6mMgq#dKVMx ha^q%Y#@a>3az|3`-r6;1y%^A322WQ%mvv4FO#tkfF|q&v literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_light_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..17c1fb921f9b7b46aaeefe7afb8302874fb0abd1 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%KAtX)As)xyURua|z<`JK;_Z5y z;?s*`91mY+Vs~LvSAIRQ|I~ek>wo_(4hk(}+Y^;`>!t%UugL`m=C=w5f(6PQ%h%~C hy?JA^CG4Uk|5eN5b2qlEYyz6g;OXk;vd$@?2>`YpFM0p~ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_shadow_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_solid_shadow_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..ddfc8e3d5c4131f2460254f183938477fc5a0679 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rhed`}n05R21qr&#kfCPg`@kT=WR_nY-)l@u)2m z9YjR(TNrC5J8zlf@MOx-MX#^q?zd~tP;1Ok`WbQLQ#YGy{=Yfu7pEGW;JmVaai5Cr S@!LSF89ZJ6T-G@yGywoUXh2*5 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..007a4b239244212339b817f8de9474a4dc34fde0 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%zMd|QAs)xyURua|z<`JK;_L&H z*gyPBj&nSGnTg$nOT)YjBQU;+O3-o%)BNS9GKrK90(RDcPwA0 ir}XBH&6co>lKfV`a~Jh`>I4G~X7F_Nb6Mw<&;$VSc`p(G literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_inverse_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_solid_inverse_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a823841c5e16f4257d18efd0832621a2f3dc355f GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%KAtX)As)xyUQ!e~V8Fq8(N;n1 z^xyoAqP_mw9VZmZ0?ruAPu{)rFFF7K literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0ad6c888b4c7e436e7d7c78432dbfdaecc95a7ac GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%Zk{fVAs)w*fBgS%&%9ECxB?U?=uVx Y$^4u51wHGE0Gi0)>FVdQ&MBb@0N@ZURR910 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ab_stacked_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..19b50abcb536602cf2cd36d5a19805464988bd20 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%PM$7~As)w*fBgS%&%9ECmdKI;Vst03^XH%m4rY literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_default_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_default_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5461b9c00fd3fc513aa4465682e70e87cca36a6d GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Ql!3HEv&)kdyQaYY4jv*T7lQS|h5*V8PD@dJb zGfWV2j%0YY@&Et-(u-Ft=sZxsVYp-gbGr;f^5z}?TUWk$0o2Ff>FVdQ&MBb@07WJt A3jhEB literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_default_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_default_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc6f804aea8ca344275ac6eb497b6bfe0f117f3 GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Ql!3HEv&)kdyQd*uajv*T7lQS|h5*V8PD@dJb xGfWV2j%0Xds4&4P&{4SYp+J&{BRiiZ!)425)ejdW>;mdy@O1TaS?83{1ORBM8sz{0 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_focused_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_focused_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a70b53c59af769e3c98973ad9718670ce27259ff GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Ql!3HEv&)kdyQYM}*jv*T7lYjjGZ_h07hy7xL zS%<;BUsuG{45zRSgeB^>bP0l+XkK D`1l&; literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..85d7aadd4dfb619883f68f1cc63e629698b5dab5 GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Ql!3HEv&)kdyQbwLGjv*T7lQS|h5*V8PD@dJT z6Fe3@|6GeFPb#ATqtK84|Mweqan0i3X%}$jvMQLwz#trO`SaWQ^{znO44$rjF6*2U FngE5o9^U`} literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__btn_cab_done_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b01e012f895bfe2c4241e1d48771fc372b35cb GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Ql!3HEv&)kdyQU;zbjv*T7lQS|h5*V8PD@dJT z6Fe3@|6GeFPb#ATqY%TTAPubyB?B2J9?cgAJee4pt{u!zH{AaisF%Uh)z4*}Q$iB} D0X-aq literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__cab_background_bottom_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__cab_background_bottom_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d8f1c8bd54f4f091e79389603095c99cf825cb6c GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%Zk{fVAs)w*Gcqy~4zxBtu(zuW zGMHg-@7EW5UBeRx4_{GYv)%pQnG{Mujhr&UNqv-pZ`Il{xB Xs2c0@Tz2whppguou6{1-oD!MWG$vw4FcCX~iP#ZL z#Ezgv#79#*s&1dojI+cAPJMQmkZrIiQwNJ5O)mOttk17mc8L&cvoMU!kQ_UoFtLbO zz!d-HXKY4=&$3fUmk1ZWI0ec-zs>!pq|Q@kJSU@WOzCRS5+-37>mm7R8Lg^F?GYkE zBoyQE_yMbobtpr21x*>PeOI?>bqDqY--SH~u7GQNNA@DjpN8vl;D9CDy?cy2$}4=} zU-<6o2fow#3cLa1FfU>%f|NY5;7l!;hc9Mfz6?Kd3>;!21TCTjzVTsyQiuOdGyKLL zkrJWzheFL)VX-6H)nFppRWZTmC)H8V-n8fBiNz#z9k9$5G%LYGc;b_$REAWCR6`m5 z*E%_MJ({2nS8v`qwS3&#TV=rb-Fv5+avqigH< znhH>dnQktv#{&`)VEGWO1G^aub9T=01LcnR UL6aF(f|Me literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__dialog_bottom_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__dialog_bottom_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2f01b1f456820ba041c0a9d1ef3a80ccf0e6c3 GIT binary patch literal 622 zcmV-!0+IcRP)Ly$}0$;qwZgs8gxBCE_n{3+(XS)eXMe`V{u9>$(f`y2WB4h!PgqLMpBaZVAZK zAZ1SgiHPAZQxkWTjWKBoiV-PC?_V6u=(8b7YMN#^5n%%1DYYM1G&i7_kTUo}oNX-) zrxO8C*EN8>GMo}V-xfT1SM|u4P?3~3T3;(<>2drW&+06RKc*K`7xcRZO#lD@07*qo IM6N<$f(6SHK>z>% literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ic_ab_back_holo_dark.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ic_ab_back_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..df2d3d158e201f4b5bc8f478bfe194c819c762d1 GIT binary patch literal 466 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah2>S z4={E+nQaFWEGuwK2hw1@3^B*n9tLu5dAc};Xq+#-xYx_sQH13|{`71Pj-*6Uoi`#n zE(U8IKgsQz<^Rb4R70ym$22iPZttV^?_#_6n&0#=x|;ZH&dfx!jETDzs(0U>)g_uQmUhGGNWo+VotE}_5b3Zj`hnf_E}Q2 zkoV^c&!VIWreR+T-g@7!2<2C~cxLXYh02~hOPyXw&7V2OC7#SEE>Kd5q8W@Hc8e16}SQ(iDxmE@S#`ha8qiD#@ zPsvQH#I3=p$s!-9K@wy`aDG}zd16s2gJVj5QmTSyZen_BP-2>S z4={E+nQaFWEGuwK2hw1@3^B*n9tLvudAc};Xq?Zzc+iX4P{i%w`)y*!lC}nMxxP5m z`ipDd|3l#c1q-`17wzs7jap-@cWTC&8}(D4%&X3GaM#!SkY4?Tswdc+PM~^5?gXjDEJr|A(9^w&s1cyzt=Kg=58+)D;SHpB%sRSHFHUToUy>RV`*x3 zp=*`d7Z@F1xvAFw#q#fW_g_%cHx(CZDFFIJwZt`|BqgyV)hf9t6-Y4{85kPs8kp-E z7={=cTNxTy8JPmPRt5&f_Zu#wXvob^$xN%nt--0uA|I$h5@bVgep*R+Vo@rCV@iHf fs)A>3VtQ&&YGO)d;mK4RpdtoOS3j3^P6 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ic_cab_done_holo_dark.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ic_cab_done_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a17b6a78920848c37a67246a76749b4cc1425a15 GIT binary patch literal 566 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr18D^?ZvQoBE&~Ijou`Xqh{y4_R~-EgIfxwlm><>K$vu5h!wc4! z9iP>XtrZVs65@3e-rKIN%Hh(x{ewWxt1j(s|L*kJ zSoUnickXv5=i7?TNH`^EJfpg4#&4;Y0qR0+drTg2JXGfUq3}Rr;{P9f_n*&gD7KkW z&D4IqLivGGRq&kmAH6(eA0`F~7S8rex88HAVS=03cY~80?cpzkFa4S5A6;t}xJ7Q# z9-saRVPfVT##U33AAO3A+Ypl|_94?@cK)tT4*8AF;-^j1iVm!a-M-<=iX&Itr@TnW zJpXO;4EOj9!Ar}#BHVL6_dHGGELXBQqN>Q8)gj~`k@s=>rw|ur?|x;upO5s^{asBY zKb@S=&SWDRsQzHyR;N3i0@8Cfnf~0l`P3PA4VeH(Ut{H!I{}l<<aB zB5gMTgH*M|HKHUXu_VC#5QQ<|d}62BjvZR2H60 RwE-$(@O1TaS?83{1OW6u*u?+< literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__ic_cab_done_holo_light.png b/com_actionbarsherlock/res/drawable-mdpi/abs__ic_cab_done_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..b28b3b54f4c81d482f797f31936cbd4013c093b5 GIT binary patch literal 552 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr18D^?ZvQoBE&~Ijfv1aOh{y4_S9ba-JBqkHTrZXEaifjpNYW7o zp-Ubd&IgJo2<&v8$G35^;Tskk#t2~^NlC?&yDV(Ca&uB1b9CBkO)cqrw|D8TKToRF zI(8j)Hm_*m)V}Gu(AmZ?>+lr!|Ew$fc6F-Mb^2PC^>5kCb?-yw@xD*CTf6%^rv51n zu|M&RMY8TuK!x`HjjgjxbG!Ig&nSGQ(EnVUqiU&qoaH15q1mNr`vuRlFlL+adKX`+ zs(G(GZ+*gGNpURMpV)N-wqLW)~-JKU!1N^uTZarp(au zRVr*pp2gnEY&GXI)J=?c?L+7E14!w(i?Kf2&j6te3n$Zi&Cq1Pnyg64!{5l*E!$ ztK_0oAjM#0U}&goV4-VZ9%5)_Wng4wYz$;u85o58|F#)LLvDUbW?ChR22(3jb0C6f zurx;QVw%w1wevX{81fiqT9$oZHH(4a215zM2{s3I28IOo7eJ#J d>KPlRN5&o6d@ywr|9haR44$rjF6*2UngC}HDAND{ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_divider_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_divider_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..986ab0b9746301f2dd9401829da09e00995621b3 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Asp9}6B-)+^BAxRtXRm= az`@Yw&#rLZUbzUUfWgz%&t;ucLK6T(%Mo}0 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_divider_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_divider_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0279e17a123f8cbb3c7e3a9ce5c5af8e693b6977 GIT binary patch literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~!k#XUAsp9}6B-)+^LX%RmN2q0 Ycy4A9FVZ~13zTN?boFyt=akR{01+Y(GXMYp literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_focused_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_focused_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..7c0599e3a6fcce1d9b22e47bfdb63afb1d3d9c02 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`6eo-U3d5>t~6?){p=$oZ!|i46oEul1LdSjZF4rt@IU_AhE`IYK8I wIB$ByaB<8!;4zVtCm{dd@wVRWcBu>u-xEd5&snyt0Gh?%>FVdQ&MBb@09FhvcK`qY literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_longpressed_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_longpressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf8e03623c94b68d31963ffe7e59c72c3dcc059 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`86o-U3d5>t~CW>mH@a{g&g;s7!l1y4Byva-KaJLVIiI)OpBYodjS ofJx&-C1HsL_x|*Yp0_#7zz}{&aC*>dO_0?Lp00i_>zopr00f~Zw*UYD literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6e77525d2dbbc1673145d60d775602c85264330d GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`6eo-U3d5>t~6?){p=$oZ!|i46oEul1LdSlBa@C*nY4{!0_J9HA2p woHspUxHx7V@R-QS6OjM!cw6swyHo~-e~lvMp^1B@1I=RaboFyt=akR{0Ay(_r~m)} literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6e77525d2dbbc1673145d60d775602c85264330d GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`6eo-U3d5>t~6?){p=$oZ!|i46oEul1LdSlBa@C*nY4{!0_J9HA2p woHspUxHx7V@R-QS6OjM!cw6swyHo~-e~lvMp^1B@1I=RaboFyt=akR{0Ay(_r~m)} literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_selector_disabled_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_selector_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..92da2f0dd3711a2ceb843768cafd6b91a2807b43 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^{6MVD!3HFkzrK_Oq*#ibJVQ8upoSx*1IXtr@Q5sC zVBqcqVMg#;uvX%d>fBtxy+SfFGJhTv9Z9>9n%%Tr7{oI+Q@DMao9hQt@O?p#8;eZd15IY| MboFyt=akR{068-?^#A|> literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__list_selector_disabled_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__list_selector_disabled_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..42cb6463e4c28c6aeffa315c4fc869867dbb6b7c GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^{6MVD!3HFkzrK_Oq*#ibJVQ8upoSx*1IXtr@Q5sC zVBqcqVMg@oJyIHz9Y|%SiynA|kd)58s-{92FmHki*;@KxND!%;H%=F4ZU(n;yg$GVRgBd(s L{an^LB{Ts54)-;@ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__menu_dropdown_panel_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__menu_dropdown_panel_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..460ec46eb0786706610e21ac9097de489cedfc33 GIT binary patch literal 651 zcmV;60(AX}P)!1&^00004b3#c}2nYxW zdZ_-Wf*5)VugS&t+23J}V*z%DOHb#W|`Z#l>;cJ*L_P;jGoKD8nX4Z`xH*Q>s zTvTl`b;%gqS1znaOc|u@tz3t7=|UK<9Hr>BJ|D1%4AOYzE>c>YASL1(e+Y^`_iG?7 z1UL~EK(|`0e*W#*UXt~C{TfUweyI5sSBLVgO?u)p&e9+4(C=i^T1GuQJ|j^LdEE0 zfZFjzm@Nd117_`XxKDqSoJXh_wG-}tW_yHE!!B|TSvyio<6k9eTgoG9O0aTdZJ81x zmbul8Z%fpkT#Wc{ND1L@No&VX#iPVF7hx`c0JdkE;3e2%ZBQYi%Oe#dj=&z+_I>|? zOJi$dCv)FoZJG3n&>Qp|;vSo*JOkf=A5uR{`;uV-QwsX>Ho!a31HXV*se5TxFFA=4 zo=3!%#5;D2+DO|6R;c8b^-B2j{s4XhZw!t1m&l3O!HmBwHn=!)kb6yF?kI1MVX*Vu z<6hz)DH{^YBPWDzO$|0isCW3@P>L98oO;C$E5=4jGEPjLS?XZ=13IvJLaC?O;nLn? z=e?6_T^b`&$N3sK+o1N3^-DyB&=+_N>geY)_rHdJwBJ%s{^4(_WBZ5MLQM53U4RfX laXn(LVYMe-Njr@(d;;;gyUim^Wn%yU002ovPDHLkV1m`SB-{W1 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__menu_dropdown_panel_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__menu_dropdown_panel_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e84adf2d41604323cdad8b15e7034b6137e02425 GIT binary patch literal 720 zcmV;>0x$iEP)!1&^00004b3#c}2nYxW zd09!jyup9SJyH%;E_KZI5Ed^B*iNJmRSj zBG=jNc2B+cMg+dzb=~X>SwfDye{PH^oqQ4$1^~*55|NVv2=ckIR8XoEqC|oOgc2g; zEPA&fNb-^8#Mr1#v(VQn5UdVR@QAWs>EP1 zTHM9Zaim5tPqqk+y4R~ii**RWgPIHWkr2Q-rv~$oD_;OFg!*vQOJ?oR%RhjSvm}}N zU}~@)A@q_TOphE%y@J$^Y;2GXG*T-_?U?-%UNYKi5n>WcgjmBq)+~pV5UB*Fc4!_E zr<_OV^tE=(@|l_%K9#lBPBbeU!!Q72CYl{ozqWE(s>}kg*Xy-fB`%jskbX;(+jyxw zA`k(lb8|^3?6_RfL_?Is~gyeh`O%xX?U(f4nJ zaCd?*qxs3xYk`99o-U3d5>wYsbmTgqz{8?`Dg4L(`abqnuZs;wTVv8ZBg|GX^f4xE z5ck=nld$UVv7H|oR+P30etdkz&;0SSW2^3}yn7y4rozh{Dy3k((DE(NNCr<=KbLh* G2~7afj5MhL literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__progress_bg_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__progress_bg_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb22f0e10e621ef31f16100b3f682a09565c65d GIT binary patch literal 159 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 RY6Dcn;OXk;vd$@?2>^cl_{IPL literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_default_holo_dark.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_default_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..29aff4d43f71a025f464587ead52aff2ecae6a58 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^59s(?1)eUBAs(G?r*GssWFX)$e`(35Y8e@;SI4XCdOETn3puLX+U@Np z`{}{9$F@!!Tb4}GnD8m&^|D{1rP|_tuSV&+Z=MCoD&l+{? zpGeMToA<;$LF?@fW~rUl3#RU765DB+Fm*PQ+)k?pQ&%&Ye!A+gR)$OeeXHaR#_cxq xcQACD7BHRDw&oXmu9s(?g`O^sAs(G?r*GssWFX)$e`(35d_EZ~i&w|H<&_`ZD(+MedV5zj zu-#_I+lqbxCf!wDY`(!qq|2YPZ`s2sD|KqW({JG~4*WL+Cpx;A`7$XPF&b}JE9dda zdDh0P2cC5-%kKDDBus2)4y)97FmX3?+)izQiL;plcWMSG#q&-%rTF<%+zn>y2h*Mj yizj?!^E*(P`ureMOz-ZiFXWg1cX%y7!FpSm=AYI1UI{?QGI+ZBxvX9s(?*`6+rAs(G?r#o^TG7xZ`zf?Wo$i4qI|8=SuS_>z)W_YPj?%Q>u zIp@ANM{x}Zqe@#*i-Jc_;!9;KcT)!P=e8Z65pgErb%iKSkCNb@A?yTt$n@r z2c`IHF%KKu<@hR|E_CSLV|hUMOZy3b87rAYm)KaVUb9s(?*`6+rAs(G?r#o^TG7xZ`zf?Wo$i2t+|9e$2v=&Zo&G1s6+_&pQ zbIyHljz-QjH&*S{-J;(gu&3N>@$LMyenNedpai4+B)&;SOq0|eu$bkReVyxuhm-Z9>GFCF_99MaBsxVXw=u8GrS3j3^P6KmrqN>Fc^oQq;b>Mt0(a$ z!tgX@A7RK4{kGy^A3#w21maN-UfpV|U0)B)Mu)Zk(%$xiP)OBD#retJ&sicWsshEL!WHUI0Z}S=i2Rqw=-{<_oUlU4$I1(Vt^KG}@Sd2k4!kUV zp9d;G14UN$xn@Z2Tv!X1WCiPlZi?T5Wo`s>dMMw4?2=HB&MqLoAdzh3Gg0e200000 LNkvXXu0mjf#CNP@ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_focused_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_focused_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6536ee63329bb47bec2bf2384aa494923cc2773a GIT binary patch literal 424 zcmV;Z0ayNsP)Km%mHHKmbPH_0G1~QE(|Z z3L*$mIy*=P@!u2&!BHHFf;fuc0uio1*2@YRBct9{0AyU2}iGVkcWw7UUPW(q{Hz&m;6;CQrhm;h30w)z2(`W_Vv zws(S3@Pah`KM=_o2%_3&n+}O86bq(Ag>`_N^4m7drC>+{^&N;72|01L0Qm!AONf3s S@;8qF0000Km%VDkFce0=d)+vOWKIG} zhpvGvb*AhYN}o4m@H1o!d4o*tRBGZMkUCTWmkL{wB~SUlAntIEgw4Gc(!&*+NcHqe zdbonr;tjwaz@#r!B?pk5-j_#K1)p#z)*b-3QShBfQxGf+4==MNa*z{HXhG0C1P_4~ zh;-k(7eP#tUz1`!Wij$Dh)MD;xve&Fv5GXEp_kweUysY@i1cm8S?~wm($ky-rf?^L z4cuB_3%3%uz>Nj2a3et6#z@uGqTe`wK1>2-pyo{<@B~f=x)vOqS_S8F z{BbGcPm`Srko*>0TM51dS0?bbgq{a9*ucpHu=A!M^DM~~^Y@aViMI>L4{MrZFUa%^ Q4*&oF07*qoM6N<$f+pdR_W%F@ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__spinner_ab_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6de0ba8841d25f20f12e14002ecc4c9ec6a7b2f8 GIT binary patch literal 370 zcmV-&0ge8NP)Kmp@CxKpa3{epU;uAYD{w zoy1-2XXsG+d5c4J(@pInLg`pK=_I6J(k2d`5V?BE{Yj@j2pl)OcgK;0qd#7akgCKEJ@PeExDlrpx^Tf8?8gJ#K-Da<_nm z+&*9_w+lGPs|OtA)dC)JfQDKAb-?t#>nbk~ygr<>n4NOeOiNA8R)UE3F)|0PZr-`P z{wS3Lh~%_Rw1t=h$DO2-+|>0Ilo0LghZEP}j*Edw;7F{TKbM3p1iOHI0}cLp61dt? QIsgCw07*qoM6N<$f;wQ9<^TWy literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_focused_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_focused_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..c9972e74bb4fc7416960e238afd47b1ac363e316 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^EI`b~!3HEJ|NhSh5-4`^4B-HR8jh3>AfL0qBeIx* zfx8og8O=|gUJDd7^K@|x;h4Gh+(zC520Y9MizoD2>~^~(JZa^V pUoEkkaeSh9w)TUlKa6vZ+qJ0**((%Vy8umK@O1TaS?83{1OR&wE2RJc literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..587337caf74f9ba3d32ba1c7cc8fb8b0b5ba245b GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^EI`b~!3HEJ|NhSh5-4`^4B-HR8jh3>AfL0qBeIx* zfx8og8O=|gUJDeo^mK6y;h4Gh+(zC53JlB#xw8$}k`45e4cLk;e3y7!_%!uv(Z^d6 t0#9x~Qa^f(Id{#0H&;LIP<)f9-+fKc+F|3Yl|VxnJYD@<);T3K0RZ_tFJ}M% literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_pressed_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__tab_selected_pressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..155c4fc753ed43185b31df3bea2af1ea5b3e7482 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^EI`b~!3HEJ|NhSh5-4`^4B-HR8jh3>AfL0qBeIx* zfx8og8O=|gUJDeo@N{tu;h33haPQZY#>U2n_RP$O>mRT6my}r8=UW!n`aACInYB!EW!u3$ rIYDzn*o$v%D|qmWd&mA1`&tG?86nH1GYjqj&0z3!^>bP0l+XkKX|61& literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a2dbf42b74f7fafb6d8a057306a9c021867d035c GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^EI`c0!3HFsSlX9@1d5$JLpXq-h9ji|$mcBZh%9Dc z;O+!rM)Q-W*8&CYJzX3_IA$jQ`2XLYnVI=;y+&GsLz;ns!JqUb2WA}}9-f|K(evAk wfFg6gPD@K@^jg!TZFujONZyK%*Ov1zxbO?iIdA+_5oi>Hr>mdKI;Vst04D7$hX4Qo literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_pressed_holo.9.png b/com_actionbarsherlock/res/drawable-mdpi/abs__tab_unselected_pressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..b1223fe3c407e072bb22d6f534397bf3254939fa GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^EI`c0!3HFsSlX9@1d5$JLpXq-h9ji|$mcBZh%9Dc z;O+!rM)Q-W*8&A?JzX3_IA$gr-1{}9v9a-?Ju~y+`p0YiB_$U2xUsRZwQat4>8lS< uMnfl1c4A|rBX^f%!~&s(1*$LFm>5oL2|PB@cz*+E5QC?ypUXO@geCw>u`ROz literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-v11/abs__progress_medium_holo.xml b/com_actionbarsherlock/res/drawable-v11/abs__progress_medium_holo.xml new file mode 100644 index 000000000..6bcbdb83f --- /dev/null +++ b/com_actionbarsherlock/res/drawable-v11/abs__progress_medium_holo.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..575334699663b221b5a2b3251572a7c7a23ddb4a GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nET98VX=kc@k8Z|>$jV8FrZ@U7ta z-*T0WQ@><>I%v71J%hiqt0P@2{q!E)%~9Ermd_VC*s;IdoBnhS>rHk6>|lcSgpVF9 tjEp}SQ%vGx1w)$%c)I$ztaD0e0sua~Gttc^?peW`&R>iA3bc>G)78&qol`;+04`QPF8}}l literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_light_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_solid_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..8155fe840532e1d0fc25450729892ea73c4e007a GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETTu&Frkc@k8Z|>$jV8FrZ@J;?q zbcL8uwC8*^`8m#29p5Ib=%`p$wC&7oqt#odO)b{rdQv>$UUk^Hs0pFVdQ&MBb@0G6aYR{#J2 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_bottom_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4d76af93de31de153c6a7d41c05496bb14d2c0 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETcuyC{kc@k8Z*AmdP~c#3C@cDZ z^MXtGfey<_%m2nWx~HUtq@RtOD!V+x#J|ag^QD{sZZM%<(R`m!o+Hzd-iC%RO>bP0l+XkKAqp+G literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6622cbad34409b2e09f69e305455482ee107baa6 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETEKe85kc@k8FK-lVFc4sN{QB_B z&fog}EEk$2&*T)$|Ch61%h`96?xa8O-xitu^~t0=vn)PNZ(|U lO#br3!H&B!tiEU`L+e4N?emv^?*rP(;OXk;vd$@?2>`X4Gjad` literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_light_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..c4272978338a232aa445ed5190abab61afcedb16 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETEKe85kc@k8FK^^+Fc4rl_yt@$W?82D^ReIqPjo#d{bLmpkiN+x lnf&F6gB^EeSbfn>hW*nwPPt#_849$O!PC{xWt~$(697sdG9dr} literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_shadow_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_solid_shadow_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d0df29d8b3fef9f71cda9b7a0975c68dcfb05685 GIT binary patch literal 290 zcmV+-0p0$IP)(^RAa&-b-qmfdL= zKY^DZ_=R_1v^+>Y{!mW=MF!v|2#Q>Q>z_&4i6T94j-Y4q`P>I~g?cE`S~^QhBCU5$ z-8a`dJ38hrt)qwm8XJx0+%VAuW=z7Jg@`1pBAUrf#Ef=wZl|UqH7RO)u1OwK(@xK~ zuV&5*5lN0GQV~igR!o15*n1TfDTP6ilQ72>QBJOyK^2jQYHlAzlrUMuFCtAA$s$=K oi)4{qME3aCu~lSF#Q4a(0o}otDK%H_Q2+n{07*qoM6N<$f*T@rc>n+a literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_dark_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d9c1b957ea4a6ce62abd120668610d0cb2bd96 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETEKe85kc@k8FK^^+Fc4rlxca~( z?t=fRa*SINS}wBuewp3mefy2x$=b4i8MC*B`RkorJG1!P69>HDUX#kpcm>9d6MZKb ky7}`x**qcrtNL{AY0h>p*ZiBQ0JN3C)78&qol`;+0O3wK)&Kwi literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_inverse_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_inverse_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..16b9bef1267adfd1cbc63961643768b968224dad GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETEKe85kc@k8FK-lVFc4sN6n$}J z)PNZ(|U kO#br3!H&B!tiEU`!|TxaCOKnM0idl6p00i_>zopr0D)>TF#rGn literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_light_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_solid_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d36f99fecf223779432fb843b823c04d739f05cb GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETEKe85kc@k8FK-lVFc4sN{Ca;{ zvU;8e%Y`P%GdTtG|K%*$a`xS%JL%8+w?$@ueKP6JEX#C%J{El7iLS?_f2=|R(l=Qo klfOK1u;Z=_t1sHgP`H~Vw5GX>6KE@gr>mdKI;Vst03NU~l>h($ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5ad475dc3f478734be31bc5763ff494e5f120914 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETR8JSjkc@k8Z!P3yFyvu&IDfHo z*MFzV3!h#&I<8{wJG6*7%`^0A*`zgvpO3}4Jy+2bzzZhGPxw0F1DlG16;n~mnRAu= eo1*Vq{$#i=98+;OAz%&ALIzJ)KbLh*2~7aXVK40f literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_stacked_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6ade5eeb37d8388813cee512f8adaad0f6c15397 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETcuyC{kc@k8FE8XhU?9M9Fk3-& z>Hkf73eN%?4n{M7dy#!$d2ZSCCv)eTUt5@6!ODmi{A7$QW>Hb7V|gN6#Q)yLqkTHN Y-hRf#&+ODnfL1Yhy85}Sb4q9e0PNo==Kufz literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_transparent_dark_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_transparent_dark_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..719b9234df6fefc32c628a212141681df3414d85 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETB2O2`kc@k8Z(Za)puoZEAojv! z?q20mzFS+?F-&~oGw)ovvN+_p>pJvXn6S<7W@Fox#)9&t;ucLK6T;ku_WZ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_transparent_light_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ab_transparent_light_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6da264db26b5debc433e570e454f7ad596d3609c GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETbWaz@kc@k8Z*Aml2;gwNxP9_w z>q*543$-17oYd6QwPclDxvFIP6Y06uZ3-`2R5T~}Ha6e^YubHkRDB$o9=Xj@xc>Lb i$;LIUf5PjFDj6>?G+3v-_OK(+N(N6?KbLh*2~7a3Fg*4E literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_default_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_default_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef2db75e273c3a4fa34a867d43714d47b67dfd9 GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^tUzqR!3HEvyN#v+DHBf@#}JFt$r%|L2@FmD6{OlE zCs-P=E^ay;FECBu-~a#X*$!!Fy;QT1l2!H>nDyu1;UEQuS2t(=%(&d{0o2dn>FVdQ I&MBb@0Ph$gIRF3v literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_default_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_default_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..2283b4c01f31c24c241101989a028a28e662ff2d GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^tUzqR!3HEvyN#v+DPvC;#}JFt$r%|L2@FmD6{OlE zCs-P=E^ay;FECA@!GWvmzw+t@{SPuqdTbmsKKbP0l+XkKVJ9JJ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_focused_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_focused_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3c909b51306d684dc9fc4deb674ab1e1feb7004e GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^tUzqR!3HEvyN#xSIG!$!Ar_~TGcqy~7@GboNVOeo z`FL&lz5>PtO@Rj=>T@VLG&w|ZbqQ*{RI`whRrVK{_2=K=E+vL!&S{*=@1z8QW-xfV L`njxgN@xNAaNr>U literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..131d1030c9d5b447ef62fc8e336d9d3950ff7519 GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^tUzqR!3HEvyN#v+DJxGG#}JFt$r%|L2@FmD6{Ok@ zwtT#{-1s4r1MA|Z!|?(x4J`lv|6k8`NJHzTnuV0CvcJHrKmQJQDKT8Re5&^Sxg#LM O7(8A5T-G@yGywoD=q9NE literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__btn_cab_done_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3e7dcdfdbaf66d51a90633e6f601bfe71b0c5069 GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^tUzqR!3HEvyN#xSIG!$!Ar_~TGcqy~7@GboNVOeo z`FL%)@k1sD*2PVS;{{wASQ@v8h$x3#@+~m2a+{}M^7C)Is4GMKie-X3Pa7xz&0z3! L^>bP0l+XkKapWP} literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_bottom_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_bottom_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd09806f5c85ad3a33ec80c2a526e9dba34d1f3 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETTu&Frkc@k8uN>rTFyLXm;P>R{ z?#-o|?nQl9Ua&YVWQl+9{Kh9CkJo#*NAA>4+xPwD`<&mEu1}vZI664|W68WNxRjX* q7uY2C$wZnF2qKv(-xu6mlE@I5@4DcHNa}B(#SEUVelF{r5}E+w(lnv~ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_bottom_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__cab_background_bottom_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..43ed26d4784aa508b93551bdb0359b959bd2c91b GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nET3{Mxwkc@k8uUzCjV8Fq8;LR1* z*j37dP53cYgnN@8iE~W|ez(m3TTj;06C0 vR9@;$usQD^o~Q2-Eg&#Ke!|DE+S9qyz8u@<_Da1C=l}*!S3j3^P6YLYC-!V(AVEFG+v!>way|q9)89ZJ6T-G@yGywpFG&bP? literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__dialog_bottom_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__dialog_bottom_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..94bb8e140facc51317500f9a499bff8d69b58c46 GIT binary patch literal 1263 zcmV_*O=ntr6ly)F+6SKuL$Ibh*R`iD@HZuVL*4g?IwI7He82<#!*f5M@I8l zCg{Dv1W$Fzcs3L;NE=$Fr<|JzVM3(@hGzPCYq!@lQdyTkKnU`?+fnp z>(F)Gr(qbrUhsW{Sj{jDU%RgR#Q1bO)1&VfVpcE~_XxE*%P(A<($9Xs|I&5c=af=+ z*;f-{87ZZ%>$=bT{r(HnQm8bA3XQA~AEs)?*yZh;9N*-9Z*zQ?kA2|bGg0jssqfwU zcKO)OozI@JYS+8!LtVxH4il#d*hFok`+bf-a|Du|<2hY81WgJ0c^9DK#Tld2{b#@1k#zP2{DlFiSLo%^KOZImV(n zX`7Gx*yrt={JtHd6D=c{0W8{i>`C0>_2LIwn9lr|5e_ce$lo*V5nAgC{+cIh8KJb0 zMu>JH%LwEf8S*}F8p|Y>31kG}DH*|M2GiBSML&ffgp|d`2+g+lsUG1Th=LOrVVL zx{0H8TpaViZ}W!mO>{JdH`1&j=9=Yrqke>kbNRG&@A26oW&&Rp$ppevt3JG-qY?ac zXlpGYUL$VLPM?~P;VjepZ&@PqY{zW}Oq$a{B)>zVPLyVG?ekjx;A zk_r5Fe8myJmI0!n;`vX^%^Jmi!B43!DQ~tRsw5IEN+lB17j|0)fT`%K8KNb`l2~h_ zd}&>v))dPGqD9RNrIM#LO`Y62;mQbjbj7pDnUfjB_HctqW-M7nd@(4lrkc(%%!VCCP)*??YAL*8RA!9349xlT>xKhZ*TuOWygd# zhMSw4-+>nR4|oBdfevVZemK?-$NFIlA%uRlS^35{( zT}BvQM-}4JU^TCykr2oTLIAHf!c^TL#7FZcO&)6muOXR1oSWPv&umVS5o82$Zr-fN zk`dDBCfi`z0U^#wNtd3EN92s)2|wM}$Il_i3*tO zZzIHMY1MHBL9_X5;L0Lh3G+o#6`1lChHH>!a6(t+YFvC=h;N2i? zC^SveHcd0P|3aLE^?J>Ey>4+d>6~7yXVZ6$a@Y*O5UUs;9v=R>yuAG5;^N|Rxm;3J zuZ1b@eB(On(r^e9wKJbNp28e&n@N_d2GnbM<5D-1lIySah4s=KKBq{SW3B z%2^H`-F018)%I~7x8)Gy8mM+H)TN2vhWOR&x92ZSjBBgLHiU?zvp;d{A7fm*v}M1x zX~%x8)7C_L{)@R^oaqd!)oN!DFC)aKzt_REShk-T8iK9Rb6LbQf(-Gw@kSa46EaTh z%y&R8^@=@ zwncC%{50n30vSPAQo*L1XM}Xfl-3rb`wUa32MxO!VJMirLfK}97<+S0pTrjqGlEwj z%M1eip^VKCo*}%UQ!Wz#`8TCq5!Q8G0j12aI3j{y6UYFk4UOiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr18D^?ZvQoBE&~JOTTd6q5Rc^#N8xy%NgI}`eR{7o3SVw~oLF?DlhO#(rS&-V`^Q!s%)gPgzJD$(6 zlPX(2b^RHIj&o60rR$>g!|G>TRxtl*e|^4a{T&6ax|r;1#_KA};b_owfAXfrk8RFR$o&QDy{Vn>#x#sulW#~7Jq)- z)TH!~FS$Xsf0AE4joN$h`JQ|K8Rwjx@86b-rgDVb@NxHUL6S>yvXNP=t#&QB{TPb^Ah ka7@WhN>%X8O-xS>N=;0uEIgTN160J|>FVdQ&MBb@0GkggF#rGn literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_ab_back_holo_light.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_ab_back_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..517e9f72d0c8d28a22360ad5d73476c25fd4db33 GIT binary patch literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr18D^?ZvQoBE&~JOTu&Fr5Rcj%_$5Ae=jmr`i z3DvOQxBn=~BCIDR#iS^(^w0Zu6&JUPm!vIx7~`u}yzledo5s8M7#uS86ZssvI8sdD z&xg3?2M7HpA7}46mr}+fqP5gidGgVwQ1_}WN$aob&9(OVStj}6Xj_!ShugM~J~IZM zU$@KD($sJ1b4JGMs?Xp43SH*aH+p$-+40a@k*+<>G~DD-|-{tS&vG^x*%GGq=j0Y+3zJN#Vil?OA~@ zukBM`R53;FYi;h*X6Nv`Uq9z3iuI~5j`7&H`BAcUqjcZh^7x-d8^2$_pe40Fq;EPa z%hqE7x*0h;3a++#0KFc4?e_Zv*^k$mEo1DpzWsL9`(v*67M^+IpR-MVa>nHwC6*2c zw&pTkoOQ_aoMO7bofkh1@}JI}ZKA~9;BBq7`S!))DS4@K0yY}FSDK>kZ?)HK*JGG& z7n!~MQ}4z!!HZux9M^8s)pB{g?A$cwGrvCg`RlIwI(LVEXW4OK`|IMz?e(5Emfz@% z$^^!kYKdz^NlIc#s#S7PDv)9@GB7mMH89sTFbpv?wlXxZGBO2ntqcr|?>AgV(U6;; zl9^VCTZ2=RMLtl2B*=!~{Irtt#G+IN$CUh}R0Yr6#Prml)Wnp^!jq{sKt&9mu6{1- HoD!MOjfK$EgnR$;T z=N`Kp(hSrM@CU%evd=bf=-4w^bQmnuhmUH-jd<6WZ; zHl4_>Rk2U@t}L_t|AiwlQ891B3C|YS1B^O3M@oxo5*z)LB91b?+QOQX_r0yH)YX2= zvK_aW4}ZVF|MIp0)1X?{@CS{e_APBtKk;H~nV$PVB{N8HV+L1tOj6 z``KDz%d4l`HAyHPxAByb%b6xG^S8m<-YfXS7v>b+zlZNGcwheZcvv-P0dQ7=MviH9v^t)=yc559VF`j43j z_pMt2oAX|`goy_Fm~UMa9d%t_^^2qU)cXl;U0e%IRZSM> z>7Ksk_VioOmKv*1mN|c~ELg&~mMikt+LpAdYoES)QG3#uo404uV`0ytJ&!XS^rKz< zU6j9{ob*(;{k73gyQmwdmG2d+82l2_J(5-Z=Epp_^THpe+eb5HIvzUo?fW(va5{7P zd0GD0trC|9zjsRjleub%YeY#(Vo9o1a#1RfVlXl=G}JY)&^0g*F*LI>FtRc>2C}UT z3_|{Y+l-q7HAsSN2+mI{DNig)WpGT%PfAtr%uP&B Z4N6T+sVqF1Y6Dcn;OXk;vd$@?2>?&0tquSH literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_cab_done_holo_light.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_cab_done_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..bb19810bc2062509e4e4968099a359ad73818728 GIT binary patch literal 915 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;rX+877l!}s{b%+Ad7K3vk;OpT z1B~5HX4`=T%L*LRfwTh{zxw0jNgl<&3j(^ zeD(x}!be9sh1LD$__>xwFX-mty1-MRJ?GVgBPRYo&#phmts!D?ltV3&FI%+a$=z1Z zV;YHl1iMSDgJ+|>#46F---v^ z=n46F|IdfFb=)S0I4Coo@!g}n4Nex)S*)#RZPN@k9D49tzo1o|L;8h-cSg(aGrNAg7qQ^lmAJNnY2BwD zmg$yB$_Kc(9`bFT@$ln4rdg7D8~IZ-f3I60`0mq%qw5167@Xn!BqR{_Lfuy~aYE2x zQ4_Q6{SU<2c z;lT8&TH+c}l9E`GYL#4+3Zxi}3=9o*4J>pG%tH*#tPG5-jE#Y8D+7a&|KB#FXvob^ z$xN$6(O_z2Y7RsY4Yp<(!9Wd?ARB`7(@M${i&7aJQ}UBi6+Ckj(^G>|6H_V+Po~-c P6)||a`njxgN@xNAiwS~- literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a92fb1d4af622cfad770d7c494121719a7896e61 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=d7dtgAr-gYUQ-leFyLX@@b&p` z%gbN)&SLHDYV<0q^J4_6fq?|&@*V6j4#NRakTDF}Z~@#RP$drMna{J{ZM0wU(H|t@ M>FVdQ&MBb@0L8N*<^TWy literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_light.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..930ca8d95e8bee5a1240fba645d9dab919abd734 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=^`0({Ar-gYUf;;epuoZ6aJO@& z%&D0-IKvGL&8p&qV;O7KuliPO1yl(K>(rHwor_8Hg9|Y9Gj8!^hz0Vk4~QZ}ABcvv nF)(akj$uTI?LshtEWgbR4Rf6*RPme*0Ev6L`njxgN@xNAEX^ml literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__list_divider_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__list_divider_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e62f011d45a2c4c61a60b6451bec014a557a5188 GIT binary patch literal 83 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=q&!_5LpZJ{Cp0wt58!#o&?v=m gn8lDKU<(7oW*(8&@UL&}fT|cgUHx3vIVCg!0O}VNC;$Ke literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__list_divider_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__list_divider_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..65061c0f45e63fe0fea0bfad9c71ee52c07ef38e GIT binary patch literal 83 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=q&!_5LpZJ{Cp0wt58!$3;rOwB g4O4-x!vO{c$tv!+&aYQD0#z}1y85}Sb4q9e02-30u5yFboFyt=akR{ E00Z(eO#lD@ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__list_longpressed_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__list_longpressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..eda10e6123e1e1383c4617228ec0c96680d60dc7 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8wRq zxI00Z(fs7;wLn1!PZ!4!jfu$#Gb-B{xqtMx1s*(O%+nLV7IxRdaaX6Iu&3070|zpW ye6d-ZyJWwc+pgOl@x443T>9p%`1p8cx&%YrImy|zOvgZGF?hQAxvX literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__list_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__list_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e4b33935a3aa4f1af3fa9e9e199b5c47d43f4b74 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8wRq zxI00Z(fs7;wLn2vPZ!4!jfu$y_kK-b literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__list_selector_disabled_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__list_selector_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..88726b69160589c8545759440e8d4e69dc984c67 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^azGr$!3HF6SgS1tQk(@Ik;M!Q+?^oIXnykaTA*No zr;B4qM&sKXhJ1$tcw7#es=J@sd2yMiyW@jyg@u(=2qh%>1r1WJE1Ls*cJ{|veUO|(2?8OR-p?Vt(<)KXNk*dJ1<_`!jRpab6RMK;Rk47fDU#NEX#7yk4(~&Fr}L7DZp)K} zK)DDZgb+dqA%qY@2q7)3S*px^wsY!a9l?~jW2$HoDfOAoDc4?$q=mJH$Q@HesW|pH zEqI5t5pt#=^`ykNb$bDA+g_!6r>E#0Ebxb)V~E)@66{HSu%o+0#^7QK=<56_<*0g z4?VWXMFRkJB8s~XX6q}?LEeWW=Ef--P!X+fp` zQ#hbt=1@CWv@?Vrj0n$wG-wfB>s2&!$QdDJ0ulZgw-cuRiR{Y>^Hj3K6cvDZyr8vA z8lxp5*y$r9!v6G_XAF7`2PhmT)XW;J_(x39;8bKfgMu`e! zU+jWXwIOOF0-q>8C*JQsY~7_bBCuA z|Ap1-2&vJi9g(t&*dI@qVtrbEm_q)8uxlUymW^N&DQrSTF2REQdw9B(B*Fk-L?96w zfdFVX0=tCgq*<4>5rKwy4p!?>V+1b~mqyqhPm^M8N}pNlWK7Aa(;L|rtB1{dT%;u; z=)dm(?jeLfE6zhUB!2hW01y9MdY)1v=ujhBx3b+Xm&>Pd9KV)clx)K|j$bdA%O^}( zPFj=H`w_B~E-2}`eIdjBd_I2}hT(GvVW{|jkQIawhG7^!pU>wnc#H_%SRreOrJP6k z#f!~#`~_1^!^gPqw^)9R`+vop3DslHn(o!PiI0dTt@|45Za=)m1`%IwEW{fV$LVaF zE9RVsAMaWMjZlg;f(R_4iy{7r`=s=`3SdP<&^M)TPHqgUw5?z25;3$902*NlyMU*S zpa+j$RH$BIEX`7~H`b|A%pl=Pj|o!QHv-L&MvImjnCmfv9y@5Gq|A_|G$L4MNvqdg zg%(@J38}FG4N!(%uhEggMhM*%9HY#I^l zO-Wr$p$z~$ref=30GgqO)XJdORd}!BEq>NeB8p9{v^kJo2|NxCQwf^HCpGf6X;Wp4 zg!hgmb$FtdtQ>ASMriqL^@|$FBEhNWjw@zMktp~+Gzo9nO1w>OhBUKE#}ER(1L)vD UI+1b(O8@`>07*qoM6N<$f~tseq5uE@ literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__menu_dropdown_panel_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__menu_dropdown_panel_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..93066c8403ddaac9b19571152ef499620bcc0e02 GIT binary patch literal 1551 zcmV+q2JrcbP)ZMyJc+a5x+ehm5NR9U3;9%`Hyz-(tJnPJWK72_ZxFO$7HJ!wl=MA(TM` z5z;&LLlB#EA&f^$3`&>pe6X)LdJ&`vfROKm-xd+jMbSqLMZ|438|}{L$EEgM9M^%w zHa{i`W4qmEM~t#0eOdu-0W1NOh@<%YUknyuBI#0I?3#!O*zRI&T@|+kOaOda%IhNv zM;99T5trXGZJ+ZJPec%b)$c$hhFbzf1btKUoQ@WxJHBtqt%Djjh@==IkkXLZmr_R= zETUs(|D2YHL30h7B}jQ1@s(0WN*y++$>%9Px0_7;Q^61da}ymGVa5oGC6I`(Se|An zXj@SBzbdSFK)Um(Fw#d8Mr2~>phE(63xW_TA*G~C`G7107k~`S4FDISDe;mJcscGO zI_^QW2+SjBmZ1U=9v>gytZ(L+77q^(-%!w_2qCD0PZix`L1IaO$h(b>d0@F@G3Eaa zi55W^GL}q#AgNiNjxa=>FZdR(r+ zxE>NCSff^sd0^fBMHn)m#E-cU=7+{dF-K!9f{S1|kOWYSoDj6e26s~&lfViRc^MJj z^>Y9iF9eSfmVwbcxJ7UPa3MH8F;)otZ%)TN7|p{_AvpQV#UO5SLMWM?>!O$+C0vaD z8(N!X1GsBg3ar^sKmkXku0dO*!9}nfXnYiL)Hg=pYKs73(^zvc9aG}t;{#B5>C;Y2 z2$?l`wfXukgc)%>9s%7AsDyH^rRNDOAHyeHMgYzG`@1jfJOi4h!RzZQK)65|cOA0& z28%R3H(uBEzOL&YU%_z=>~=fscDp^gsmfTk*1@j+TejVzGC zpG{n`bp4r^(iKT*Io^&x%OHc_WZd_EkhCG`k)*sFZ|A&V23>!#)HToxrIRJ-1q1Q~ z3qHxB_z~+bgihSWK3otqlC9O%9BoMC(eAot5j0oOB3|pwbjUE4*ONZd@_Kxq z*B8(0XhE_FfV`ZZwcn~gXn8&5;sQ&h03_lPHzc@QD2L`A0DB!p$qE$r97Hn6cmgfM z$;^GEI@&qEx+o;VEJ0ZAK#dKOL6F|xU|IwgE6|`&x_D{{k^myy5)6fQcS{yR>LIlD zb0eL(UISu`k;Dp-=L_3_9uII4xG36?ZZ5uUxd1Up2yq&z=dNHjN&oIh<{D_CLJUbUG4w?002ovPDHLkV1n`$ Bwt@fv literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__progress_bg_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__progress_bg_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..345f5d3067c1b5a2b13f7234238468e8083e75e8 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^Ahr?*8<0#p>+uXou@pObhHwBu4M$1`kk47*5n0T@ zz}*SLjOHg#uLTMQc)B=-SoFS~;>gutz{8S$H2ll||1$b2>+ab^8r zChoU9-sVBr1cr+EHAcpdxgPN?*fw!WN@w!F_di=AUe22-xp-2_jNr43yl#?i1$i|o QK(iS+uXou@pObhHwBu4M$1`kk47*5n0T@ zz}*SLjOHg#uLTPFdAc};SoFS~;>gutz{8S$^!?BO&wHlFT~V_1=)TZ8!I{mdT_A)Z zztl5%UbEjFE&+!7%~#Kq{9)d)y{;#I@4EEH*bSem)2!T7Pp)1*OL*gdvC7^*b6)~Y OX7F_Nb6Mw<&;$Uil{z^9 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__progress_primary_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__progress_primary_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..c6c3f1ec248835c16ee8a8f9d253769ea4196468 GIT binary patch literal 1309 zcmV+&1>*XNP)>2T29Hys?P&Vm zZA9*OOSNlf`Pu!y*8AUEO|D+e=9Pi3BR)agfw#}SukU>S5_JQH{fFX7qt#y26-7>F z^lg#%AL~){ay($Vl%dNK|ns=8F95J5u4IuT)_iO}FO5AO!x2YA-ev{((MiCMb95Z27y1N{d~<$0ZOiAzT-4$^Y~d8=D&GctVLiOE-HZCX=>916 zaB}eD93BUF6!cN|FY@&i`W7!}IIL?H*U-;b(9ZeC>T4fo-D~%;%Al$SM;o1{CK^c@ zLsL?U%;F5uW`mKV6p@jG8Wm~81rvcAg~E0r38^#=q@={j6{7*N{-mRal zZyi757l1u@+(UUVUpKQfbx4*^AEZ`NBGTk&djR!;W=;+8vR!2ZvkZUiF_Uw;@^@UHY_tFNo|bZ|HqK}~Z_0kB z>1@!nFOf~^{KOi&lvG5Os4i`3m5LaZ>S{(}C2cS_3u25qx+GSC0x^F*mC<6C%zjX} z`(@Gi@=yT|C0D;bu>bAn+W30Hh5ZfH4VVpRc=$wP71`a_ciV~TTQkNXRQHQat7toU zZx*3Z?Q~QYQO|ZUR_Tl#qXtzIiPWWo(#}&aV(i6KuEST$lYVRUVscgaEz?@VCh+@z zYdAdahv=OS&fP(?9$16?`l0%F3gfnP`B71?hw4@2e4pjZouU=>X|R*`N-YD-DJj}uU$#5 zV>X}RzD~bXHhFf?KK=4JTCWrMiS*`U`G2n9l*Y@cp7B$uQwuKJNIF$*XNP)>2T29Hys?P&Vm zZA9*OOSNlf`Pu!y*8AUEO|D+e=9Pi3BR)agfw#}SukU>S5_JQH{fFX7qt#y26-7>F z^lg#%AL~){ay($Vl%dNK|ns=8F95J5u4IuT)_iO}FO5AO!x2YA-ev{((MiCMb95Z27y1N{d~<$0ZOiAzT-4$^Y~d8=D&GctVLiOE-HZCX=>916 zaB}eD93BUF6!cN|FY@&i`W7!}IIL?H*U-;b(9ZeC>T4fo-D~%;%Al$SM;o1{CK^c@ zLsL?U%;F5uW`mKV6p@jG8Wm~81rvcAg~E0r38^#=q@={j6{7*N{-mRal zZyi757l1u@+(UUVUpKQfbx4*^AEZ`NBGTk&djR!;W=;+8vR!2ZvkZUiF_Uw;@^@UHY_tFNo|bZ|HqK}~Z_0kB z>1@!nFOf~^{KOi&lvG5Os4i`3m5LaZ>S{(}C2cS_3u25qx+GSC0x^F*mC<6C%zjX} z`(@Gi@=yT|C0D;bu>bAn+W30Hh5ZfH4VVpRc=$wP71`a_ciV~TTQkNXRQHQat7toU zZx*3Z?Q~QYQO|ZUR_Tl#qXtzIiPWWo(#}&aV(i6KuEST$lYVRUVscgaEz?@VCh+@z zYdAdahv=OS&fP(?9$16?`l0%F3gfnP`B71?hw4@2e4pjZouU=>X|R*`N-YD-DJj}uU$#5 zV>X}RzD~bXHhFf?KK=4JTCWrMiS*`U`G2n9l*Y@cp7B$uQwuKJNIF$+uXou@pObhHwBu4M$1`kk47*5n0T@ zz}*SLjOHg#uLTN5dAc};So9|U`2XLYnVI=;y-5$#f-D2o4GsT&Tr`prfMCHsr=)`p zy9zZlQWAk&gL|Kjh|gcN+0MxMkn!S`=@A+#%ic(Om>d23|9^ht#EF9It^fc3f1ZP3 Y!dJ=4-o~HPfwnMsy85}Sb4q9e06h#o+yDRo literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__progress_secondary_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__progress_secondary_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..205b66e2cdef686c5ed6369b14e64b38d0182984 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^Ahr?*8<0#p>+uXou@pObhHwBu4M$1`kk47*5n0T@ zz}*SLjOHg#uLTN5dAc};So9|U`2XLYnVI=;y-5$#f-D2o4GsT&Tr`prfMCHsr=)`p zy9zZlQWAk&gL|Kjh|gcN+0MxMkn!S`=@A+#%ic(Om>d23|9^ht#EF9It^fc3f1ZP3 Y!dJ=4-o~HPfwnMsy85}Sb4q9e06h#o+yDRo literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_20_inner_holo.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_20_inner_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..76e9428beb16662de7de3bb3d9348be4760f3864 GIT binary patch literal 1160 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU0wmSG7d!(}OiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr18J~chQ$R_elRdFPw;ec4DmSr_S$Lxm{1w^5AVOxdhOSw6#mu*-Q!TILX?#LYe@iQOa?I{e`UZx9V#iwBB*NKlFT13r5HiU~ z)OyPeE}nkt_xG>2E-_@*!4 zQ-90g*O?DSc|22Jv;5_#E||ssBH~GI+Rg)i3NlY6PtmkH;_P$bF_&6p;=oh_Fj0_K90GevLS= z#Ex&{-18fopXls+`pEir(5|)OmhH#CEK%CESg`jflV!q+rq_Skf<^iYCUm~tdoQiH zGvhe7N0OlO49}4Nwb?cO;IZ|v+N;i*tUdXR>)V?0gRJwGpBMQp$;pskZMAI2HPdyE?#VFh$<=%OjN_Y; zB!kvc_OmZ{-SxTpoUuatd}jEQy7CnE3T_Wg&CNYtrPT~~WUfmbd*~m_%W+dY9JyStbiIYVSNhSGu{mz*tY5fyr#S&U zR9&sK=2uepA6MJ*ynCK0mo{;K!b4t`CikV4(pycpzUuiJcQe(!&UJd(`Og*y3;Xu& zdz*8Ao98c|;9V;gMU`HY`>$E3IpMd}k-YN0{!1UF&KK8KzwcYw>t5=#TxVt1-*xe; zC7-UZgPInwXDCdwk|m}>&lDO4YwCRra zntLs+uyWevme?b=v{x~+9P8L3dzCYL!%K16KjH+7V8c~vxSdwa$T$Bo=7>o=I4RsAHbqy^;3{9DSr1<%~X^wgl##FWaylc_d9 OMGT&hxpZ9^6p0Xj@_! z_~T9h3uB8w6R%S$bC&ss{0H(Eazi;C&F^k-NMaNUid1x1Gr{4JvSUrFLzD*Z!Hu&0 z>2YGA8|PnYez7jpe3YOZ>S)!hKWEzF1XvE#Uk#R=rFeK|w`D z!4JyGPb3oUe@m(!baf=>d{x2sU3v8GX@Z#x=$L7mU z7SCE(kuXgpK=BP{yI}uL&)(QKeT=6RyS+V3H`e&3G-v;Do~C>@&Rn!ArC`Oy*>|7L zcqrJW%d?G+L@?0$MdUtx4zaiwlf@JB6F=XXoP`cb<1e=G@J$cAq9R`3XnGxnR5FzIl;ea~?3f zp7D@>S@^RVGrkypGw6A0ANQe)i*e$^*R^{aJ--Dq%`2Lk{`T_SBU%z^`pHWKz4xT| zaWPKgVclkQPC+uu=80ADT)r^P$(wT0#1H@2u_HpsrY1&4r!=m(edW}x-b_uY4TWkN z1|HKy_!jxi{BHA5^66E*O-t@u_A?g!u#j+CrL!TSYR{2tkD?!Q-}<5wv!|-;9WXzr zmbgZgq$HN4S|t~y0x1R~14Bby14~^)%Me3TD`PV&6AN7fb1MS_MvFb6C>nC}Q!>*k zacd|@WqAqIAPKS|I6tkVJh3R1!7(L2DOJHUH!(dmC^a#qvhZZ84Nwt-r>mdKI;Vst E0C*myI{*Lx literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_default_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_default_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d8929fcd1864e92c78f24d34bb07ac0304bf5ebe GIT binary patch literal 395 zcmV;60d)R}P)%)khMb;v@5Yo)-?A$vWI3J{&4Tqm)VM&;#i3-!rXQ;}}A|kSK_xLoQ9!D=| zrQuRX_jo!D&!*woG(4MzXVdU(8lG)+!_Dl;%pOYTRyEwrUVwMtwRCnh!_9059DxJy zTsprAUsT~zdJlX8e>K+(FMd_}2<&RF8#?@^bn(8vHtbfpl-_eLJ!>=!D!!z&OE12( zv`a1CqqIva-mA1rDc-ZR3oqWgvUXO(tc zif5K~i;HKMc8iLSDD9eyk1Fk&iksP%L8V=5@ekmQ^NVfO%k2z5z^Qbob@&(X-FR)u p3HS~A?&0(Ut%!)oJooy?_kZokYAURbJh}h?002ovPDHLkV1n7-uz&yn literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_default_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_default_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..9174c4e4bc984a89e1ed643bc66b1569466ef52b GIT binary patch literal 394 zcmV;50d@X~P)pS_@$ zhD#gW|+S$zvH?tjZ06u}I z+WCj@MGelS_rM46*K*DD;uob4z^?VWsl%^ISMTdf({7b>={={?i$=qw;%iE~^x|tv zyVT+%O1re;qe{D!;v-AD@ZzIOyU^khrCnI@sM0Q^cw}ieym)kJH?(+0X*aBRR%th+ zcxGw0x_EYJx2pJz(yqJstkSNlxS72&skG}Y{tg(Y7u%+n+Xa4rW9?Ay@Gs!I^V*st o@Ei2q)9D9V5fPDPZuO7v|4Ms3SH1lZVgLXD07*qoM6N<$f(F>P&Hw-a literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_disabled_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3015d307088f12d52a9e99ff4575fd2153127e5b GIT binary patch literal 381 zcmV-@0fPRCP)hG*08Y#N?T!?S64w!;lKvy+*1?>p3RGdlx#190x$dzj&7b^-9QUftoB@I?dG z(l;!l|3;oEFaA*ajtwD8hd-6>9*;+5M`bO2!%}+FXrQS0p3;u)|QJUO}XO$+G;+dssck%4fw5xbU zX__uxRhp)XSC*#P;_m>iSbo@k`^^CW{9xcU{p|2h0AG{O_A~&$L7!btFK9(XM7DX= bKfeD1Etg?&Fp}qT00000NkvXXu0mjfW#pxI literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_disabled_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_disabled_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..126637d1194f1d6609787774fb140818eaa4ba1f GIT binary patch literal 381 zcmV-@0fPRCP)hG*08Y#N?T!?S64w!;lKvy+*1?>p3RGdlx#190x$dzj&7b^-9QUftoB@I?dG z(l;!l|3;oEFaA*ajtwD8hd-6>9*;+5M`bO2!%}+FXrQS0p3;u)|QJUO}XO$+G;+dssck%4fw5xbU zX__uxRhp)XSC*#P;_m>iSbo@k`^^CW{9xcU{p|2h0AG{O_A~&$L7!btFK9(XM7DX= bKfeD1iaRu(IeFS@00000NkvXXu0mjfR7Ip< literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_focused_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_focused_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d45c7a864d9b36fc5d06ef7650bd22c228e3533e GIT binary patch literal 680 zcmV;Z0$2TsP)JMg@Fn128fNR{httc z2n-9ae~A~-WYQOAyjJ3`v+tFS>H?h>cjZI44!YCDJ4vU-UH-NFC!H?dNqTY9^x~%J z#ZA+To2C~xO)qX5=ft;__PrJUYC9#q>!rbyQFMFHPb=3hoe|&n)9~p|baWUbMthlMtLL6k^E5&wx9H zfkWKo4DxGy27JSv%4KLW#~I|sPnXY+fCHfNj(}ks%-+uyBgtHbMG(R)| O00009q_DOpcx|jv*P1Z)f>u3p>g<+)ulGcHZsoOUd0`v$ideb3Xs*rv6J! zg}R0?Ejbavstni13%M+(h~#=Y?=G{+sY|c-R?LyC;M~J_EGz53I-4BR z+cU4@A4T%4{LQ{7{nooe_R?;9Ufq56+wp;!Z_W|LdHU^V zuiix!E)rt*mg%eid+||lw)K3kxzR2XQeoHKTo0e>Tz_Z7KF;I+KDl^G6{a%9AKj}v z@Aq1v55LRoZXbD)<16^lF!*Li$^7}+)mu{zFEMCaSs}k;w&aatvA+!FKCV6>P_Fen z(L?roLe!sxPkWei7+T9&rtD<0W7^%ZdcE}hdZyQpKhHVIBAEwFVGN$GelF{r5}E)^ Cjyh@p literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_pressed_holo_dark.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..2cb34d7f60401a563454c03e266cc5181d9de996 GIT binary patch literal 609 zcmV-n0-pVeP)%5ec8;(Nh#y0Zthco9&fD+Scz(J_kqCSd>@N)Jrb#iJx@ z@geV)|0U|;Q8J4AW)%0$DDIn4+&81RZ$@$73{E^#CTa6Odr;zqGLyGk)zfLB)26Y5 z5#LU9dOW?JALQjc$4r{WH?GVFg))<)qMW_Ts@W~*Hgkd(m(u%LwVNxGwVfAK{BG$~ zHD4Zg33jP$CptYU%GrKa?RJ%hK*cvHEqE`!X=%Z0aT}!tZ^dnu7Q7Uk{&1!u)wl@^>7cUfAnUfgwQ!D?|Ir3GuneU%oh6!%$L*todw(!xf?LzEVJ7Y|ih z=v92GOd6`R(5v{nqI~u|T|T)5eQi`_Q>fI*QllCl)AthpQf>YCTIE0c%xyxW%x|6C zG>z*e{-?!>T@(EFL|;a6&!||%qb(l4cp2298zt1@mjOk(atXEgIq-{NzY%9xIi5qk vb6%{!-vcjz*7vDBaRYn@J|M2KzWDwRiVUw(_ikNA00000NkvXXu0mjfOP&sp literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_pressed_holo_light.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__spinner_ab_pressed_holo_light.9.png new file mode 100644 index 0000000000000000000000000000000000000000..82f752fdc28390f1dae188e66886f9fee783b4f7 GIT binary patch literal 602 zcmV-g0;TXoV&|>P?2N3a|H=XkIpb%g2C{mUcD8z%K5!WW71`A3B@}P?c zNzmeTUM-&`=;A@rh-=e`Ytx8p(}-)+h-=e`Yg0e*Nfwct-zMuNo?gfN+FNCOm6(Rs zNBrh8;rK_F^Mfu~8k>e2?@WdCI_7lHVR+IZj_>A~;=j0*zBpPVOJB@8&9C^w(v!^i zcs#`4qjGbZa60I4akN%e8hjOBptPW0e8JL!ZgC5x1-;^yN((y0EtVGC7q?tma9i9# zX~A7_N2LWf#T}Lw>=$=jTCiJOp|oJHxKe4sPI1N3!s5l1OACt?_fT4>Uffe@p<40r zRpP6(P_6jSy?2bx-x9}n#O>6o(Q^BiC^BxWR1^RB?JvEr|0>Nj5k)!eSD9&8HSzXK z2sbij-vth4?P;pL%~0mos(Tg<|DAn;kcS obzts&Y9%t@53qx{hWg?AJqc6disFjI`v3p{07*qoM6N<$f`4nJ zaCd?*qxs3xYk`8Mo-U3d5>t~6?){q5*x2~c-pCBZ5@_=ERFyG literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e4229f26b2771d884934b80d0056b8dd66d10edd GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol;0U|59*B=E^EX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`8+o-U3d5>t~6?){q5*x2~c-l(n1@K5>ymj5$1a2Oc?!34d7Ck`Aq skg(^gW|wXH-lZoEvTk@AJm}J3sQWAGentL}KF}BjPgg&ebxsLQ0KFYBT>t<8 literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_pressed_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__tab_selected_pressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e862cb12154541c150fb2d9bb98872bcff506317 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol;0U|59*B=E^EX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`8Mo-U3d5>t~6?){q5*x2~c-pCBZ5(#$xWo$;KAD39oi<@P`m7(8A5T-G@yGywqAKq?vl literal 0 HcmV?d00001 diff --git a/com_actionbarsherlock/res/drawable-xhdpi/abs__tab_unselected_holo.9.png b/com_actionbarsherlock/res/drawable-xhdpi/abs__tab_unselected_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..9465173781eaa7fc9ab0e191904f928ad1c0b8f4 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ#0V1dK=^FznmSQK*5Dp-y;YjHK@;M7UB8wRq zxI00Z(fs7;wLn1+PZ!4!j+x0n{{OdUW@bKIZ*t~9!x;yr|1&pm7#RS;1ieP?Kk11H z2?+-Ger=J9{IX`d2{ZSUyAFF~k44WnGFVV~q2hHg8$(d6=$w>Y|ILABGI+ZBxvX + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__btn_cab_done_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__btn_cab_done_holo_light.xml new file mode 100644 index 000000000..b2984f2e7 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__btn_cab_done_holo_light.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_dark.xml b/com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_dark.xml new file mode 100644 index 000000000..2588a492d --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_dark.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_light.xml new file mode 100644 index 000000000..e2078c967 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__ic_menu_moreoverflow_holo_light.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__item_background_holo_dark.xml b/com_actionbarsherlock/res/drawable/abs__item_background_holo_dark.xml new file mode 100644 index 000000000..a4c625cc5 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__item_background_holo_dark.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__item_background_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__item_background_holo_light.xml new file mode 100644 index 000000000..f989197ad --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__item_background_holo_light.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_dark.xml b/com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_dark.xml new file mode 100644 index 000000000..b2ce4f0f7 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_light.xml new file mode 100644 index 000000000..d7e31b1d1 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__list_selector_background_transition_holo_light.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__list_selector_holo_dark.xml b/com_actionbarsherlock/res/drawable/abs__list_selector_holo_dark.xml new file mode 100644 index 000000000..ee497a239 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__list_selector_holo_dark.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__list_selector_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__list_selector_holo_light.xml new file mode 100644 index 000000000..e1522ee43 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__list_selector_holo_light.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_dark.xml b/com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_dark.xml new file mode 100644 index 000000000..bd19140ab --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_dark.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_light.xml new file mode 100644 index 000000000..321f07c8b --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__progress_horizontal_holo_light.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__progress_medium_holo.xml b/com_actionbarsherlock/res/drawable/abs__progress_medium_holo.xml new file mode 100644 index 000000000..6d4814f86 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__progress_medium_holo.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_dark.xml b/com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_dark.xml new file mode 100644 index 000000000..4af5e22a9 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_dark.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_light.xml b/com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_light.xml new file mode 100644 index 000000000..b78508478 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__spinner_ab_holo_light.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__tab_indicator_ab_holo.xml b/com_actionbarsherlock/res/drawable/abs__tab_indicator_ab_holo.xml new file mode 100644 index 000000000..d34e20811 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__tab_indicator_ab_holo.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/drawable/abs__tab_indicator_holo.xml b/com_actionbarsherlock/res/drawable/abs__tab_indicator_holo.xml new file mode 100644 index 000000000..61f76efc6 --- /dev/null +++ b/com_actionbarsherlock/res/drawable/abs__tab_indicator_holo.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout-large/abs__action_mode_close_item.xml b/com_actionbarsherlock/res/layout-large/abs__action_mode_close_item.xml new file mode 100644 index 000000000..8811dad8d --- /dev/null +++ b/com_actionbarsherlock/res/layout-large/abs__action_mode_close_item.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/layout-v14/sherlock_spinner_dropdown_item.xml b/com_actionbarsherlock/res/layout-v14/sherlock_spinner_dropdown_item.xml new file mode 100644 index 000000000..6c183c059 --- /dev/null +++ b/com_actionbarsherlock/res/layout-v14/sherlock_spinner_dropdown_item.xml @@ -0,0 +1,26 @@ + + + diff --git a/com_actionbarsherlock/res/layout-v14/sherlock_spinner_item.xml b/com_actionbarsherlock/res/layout-v14/sherlock_spinner_item.xml new file mode 100644 index 000000000..61dc02527 --- /dev/null +++ b/com_actionbarsherlock/res/layout-v14/sherlock_spinner_item.xml @@ -0,0 +1,26 @@ + + + diff --git a/com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar.xml b/com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar.xml new file mode 100644 index 000000000..040df44ab --- /dev/null +++ b/com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar_overlay.xml b/com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar_overlay.xml new file mode 100644 index 000000000..c64ef141b --- /dev/null +++ b/com_actionbarsherlock/res/layout-xlarge/abs__screen_action_bar_overlay.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__action_bar_home.xml b/com_actionbarsherlock/res/layout/abs__action_bar_home.xml new file mode 100644 index 000000000..5c1e9ec4b --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_bar_home.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__action_bar_tab.xml b/com_actionbarsherlock/res/layout/abs__action_bar_tab.xml new file mode 100644 index 000000000..f46f7a044 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_bar_tab.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/com_actionbarsherlock/res/layout/abs__action_bar_tab_bar_view.xml b/com_actionbarsherlock/res/layout/abs__action_bar_tab_bar_view.xml new file mode 100644 index 000000000..0d51220c9 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_bar_tab_bar_view.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/com_actionbarsherlock/res/layout/abs__action_bar_title_item.xml b/com_actionbarsherlock/res/layout/abs__action_bar_title_item.xml new file mode 100644 index 000000000..dd69acada --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_bar_title_item.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__action_menu_item_layout.xml b/com_actionbarsherlock/res/layout/abs__action_menu_item_layout.xml new file mode 100644 index 000000000..13149fd63 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_menu_item_layout.xml @@ -0,0 +1,56 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__action_menu_layout.xml b/com_actionbarsherlock/res/layout/abs__action_menu_layout.xml new file mode 100644 index 000000000..7010bcce9 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_menu_layout.xml @@ -0,0 +1,23 @@ + + + + diff --git a/com_actionbarsherlock/res/layout/abs__action_mode_bar.xml b/com_actionbarsherlock/res/layout/abs__action_mode_bar.xml new file mode 100644 index 000000000..7168dc77f --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_mode_bar.xml @@ -0,0 +1,24 @@ + + + diff --git a/com_actionbarsherlock/res/layout/abs__action_mode_close_item.xml b/com_actionbarsherlock/res/layout/abs__action_mode_close_item.xml new file mode 100644 index 000000000..875ec3e1b --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__action_mode_close_item.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__dialog_title_holo.xml b/com_actionbarsherlock/res/layout/abs__dialog_title_holo.xml new file mode 100644 index 000000000..6402f28be --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__dialog_title_holo.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__list_menu_item_checkbox.xml b/com_actionbarsherlock/res/layout/abs__list_menu_item_checkbox.xml new file mode 100644 index 000000000..39aca3a8d --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__list_menu_item_checkbox.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__list_menu_item_icon.xml b/com_actionbarsherlock/res/layout/abs__list_menu_item_icon.xml new file mode 100644 index 000000000..55ab28a24 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__list_menu_item_icon.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__list_menu_item_layout.xml b/com_actionbarsherlock/res/layout/abs__list_menu_item_layout.xml new file mode 100644 index 000000000..147f36fe8 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__list_menu_item_layout.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__list_menu_item_radio.xml b/com_actionbarsherlock/res/layout/abs__list_menu_item_radio.xml new file mode 100644 index 000000000..ff54bbecd --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__list_menu_item_radio.xml @@ -0,0 +1,24 @@ + + + + diff --git a/com_actionbarsherlock/res/layout/abs__popup_menu_item_layout.xml b/com_actionbarsherlock/res/layout/abs__popup_menu_item_layout.xml new file mode 100644 index 000000000..d42425ad3 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__popup_menu_item_layout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__screen_action_bar.xml b/com_actionbarsherlock/res/layout/abs__screen_action_bar.xml new file mode 100644 index 000000000..17fd16c22 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__screen_action_bar.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__screen_action_bar_overlay.xml b/com_actionbarsherlock/res/layout/abs__screen_action_bar_overlay.xml new file mode 100644 index 000000000..657aeb794 --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__screen_action_bar_overlay.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__screen_simple.xml b/com_actionbarsherlock/res/layout/abs__screen_simple.xml new file mode 100644 index 000000000..33e2dea0d --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__screen_simple.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/layout/abs__screen_simple_overlay_action_mode.xml b/com_actionbarsherlock/res/layout/abs__screen_simple_overlay_action_mode.xml new file mode 100644 index 000000000..58bafaf1e --- /dev/null +++ b/com_actionbarsherlock/res/layout/abs__screen_simple_overlay_action_mode.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/com_actionbarsherlock/res/layout/sherlock_spinner_dropdown_item.xml b/com_actionbarsherlock/res/layout/sherlock_spinner_dropdown_item.xml new file mode 100644 index 000000000..a6c6252d2 --- /dev/null +++ b/com_actionbarsherlock/res/layout/sherlock_spinner_dropdown_item.xml @@ -0,0 +1,26 @@ + + + diff --git a/com_actionbarsherlock/res/layout/sherlock_spinner_item.xml b/com_actionbarsherlock/res/layout/sherlock_spinner_item.xml new file mode 100644 index 000000000..bea740178 --- /dev/null +++ b/com_actionbarsherlock/res/layout/sherlock_spinner_item.xml @@ -0,0 +1,26 @@ + + + diff --git a/com_actionbarsherlock/res/values-land/abs__dimens.xml b/com_actionbarsherlock/res/values-land/abs__dimens.xml new file mode 100644 index 000000000..502cc16a3 --- /dev/null +++ b/com_actionbarsherlock/res/values-land/abs__dimens.xml @@ -0,0 +1,33 @@ + + + + + 40dip + + 4dip + + 16dp + + 12dp + + -2dp + + 4dip + diff --git a/com_actionbarsherlock/res/values-large-hdpi-1024x600/abs__dimens.xml b/com_actionbarsherlock/res/values-large-hdpi-1024x600/abs__dimens.xml new file mode 100644 index 000000000..3312cfa7f --- /dev/null +++ b/com_actionbarsherlock/res/values-large-hdpi-1024x600/abs__dimens.xml @@ -0,0 +1,33 @@ + + + + + 48dip + + 8dip + + 18dp + + 14dp + + -3dp + + 5dip + diff --git a/com_actionbarsherlock/res/values-large-land-hdpi-1024x600/abs__dimens.xml b/com_actionbarsherlock/res/values-large-land-hdpi-1024x600/abs__dimens.xml new file mode 100644 index 000000000..502cc16a3 --- /dev/null +++ b/com_actionbarsherlock/res/values-large-land-hdpi-1024x600/abs__dimens.xml @@ -0,0 +1,33 @@ + + + + + 40dip + + 4dip + + 16dp + + 12dp + + -2dp + + 4dip + diff --git a/com_actionbarsherlock/res/values-large-land-mdpi-1024x600/abs__dimens.xml b/com_actionbarsherlock/res/values-large-land-mdpi-1024x600/abs__dimens.xml new file mode 100644 index 000000000..3312cfa7f --- /dev/null +++ b/com_actionbarsherlock/res/values-large-land-mdpi-1024x600/abs__dimens.xml @@ -0,0 +1,33 @@ + + + + + 48dip + + 8dip + + 18dp + + 14dp + + -3dp + + 5dip + diff --git a/com_actionbarsherlock/res/values-large-mdpi-1024x600/abs__dimens.xml b/com_actionbarsherlock/res/values-large-mdpi-1024x600/abs__dimens.xml new file mode 100644 index 000000000..35910333b --- /dev/null +++ b/com_actionbarsherlock/res/values-large-mdpi-1024x600/abs__dimens.xml @@ -0,0 +1,36 @@ + + + + + 56dip + + 4dip + + 18dp + + 14dp + + -3dp + + 9dip + + + 64dip + diff --git a/com_actionbarsherlock/res/values-large/abs__dimens.xml b/com_actionbarsherlock/res/values-large/abs__dimens.xml new file mode 100644 index 000000000..63b12f7f3 --- /dev/null +++ b/com_actionbarsherlock/res/values-large/abs__dimens.xml @@ -0,0 +1,29 @@ + + + + + 55% + + 80% + diff --git a/com_actionbarsherlock/res/values-sw600dp/abs__bools.xml b/com_actionbarsherlock/res/values-sw600dp/abs__bools.xml new file mode 100644 index 000000000..7a48e1542 --- /dev/null +++ b/com_actionbarsherlock/res/values-sw600dp/abs__bools.xml @@ -0,0 +1,19 @@ + + + + + false + diff --git a/com_actionbarsherlock/res/values-sw600dp/abs__dimens.xml b/com_actionbarsherlock/res/values-sw600dp/abs__dimens.xml new file mode 100644 index 000000000..f67853817 --- /dev/null +++ b/com_actionbarsherlock/res/values-sw600dp/abs__dimens.xml @@ -0,0 +1,38 @@ + + + + + 56dip + + 4dip + + 18dp + + 14dp + + -3dp + + 9dip + + 5 + + + 64dip + diff --git a/com_actionbarsherlock/res/values-v11/abs__themes.xml b/com_actionbarsherlock/res/values-v11/abs__themes.xml new file mode 100644 index 000000000..03473572c --- /dev/null +++ b/com_actionbarsherlock/res/values-v11/abs__themes.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/com_actionbarsherlock/res/values-v14/abs__styles.xml b/com_actionbarsherlock/res/values-v14/abs__styles.xml new file mode 100644 index 000000000..f2aa64d2d --- /dev/null +++ b/com_actionbarsherlock/res/values-v14/abs__styles.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/values-v14/abs__themes.xml b/com_actionbarsherlock/res/values-v14/abs__themes.xml new file mode 100644 index 000000000..f8844ad25 --- /dev/null +++ b/com_actionbarsherlock/res/values-v14/abs__themes.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/values-w360dp/abs__dimens.xml b/com_actionbarsherlock/res/values-w360dp/abs__dimens.xml new file mode 100644 index 000000000..6f49d7e47 --- /dev/null +++ b/com_actionbarsherlock/res/values-w360dp/abs__dimens.xml @@ -0,0 +1,22 @@ + + + + 3 + diff --git a/com_actionbarsherlock/res/values-w480dp/abs__bools.xml b/com_actionbarsherlock/res/values-w480dp/abs__bools.xml new file mode 100644 index 000000000..3eaf4aee9 --- /dev/null +++ b/com_actionbarsherlock/res/values-w480dp/abs__bools.xml @@ -0,0 +1,22 @@ + + + + true + false + diff --git a/com_actionbarsherlock/res/values-w480dp/abs__config.xml b/com_actionbarsherlock/res/values-w480dp/abs__config.xml new file mode 100644 index 000000000..88357b0a7 --- /dev/null +++ b/com_actionbarsherlock/res/values-w480dp/abs__config.xml @@ -0,0 +1,29 @@ + + + + + + + + true + + diff --git a/com_actionbarsherlock/res/values-w500dp/abs__dimens.xml b/com_actionbarsherlock/res/values-w500dp/abs__dimens.xml new file mode 100644 index 000000000..2fd4deea2 --- /dev/null +++ b/com_actionbarsherlock/res/values-w500dp/abs__dimens.xml @@ -0,0 +1,22 @@ + + + + 4 + diff --git a/com_actionbarsherlock/res/values-w600dp/abs__dimens.xml b/com_actionbarsherlock/res/values-w600dp/abs__dimens.xml new file mode 100644 index 000000000..b085952d3 --- /dev/null +++ b/com_actionbarsherlock/res/values-w600dp/abs__dimens.xml @@ -0,0 +1,22 @@ + + + + 5 + diff --git a/com_actionbarsherlock/res/values-xlarge/abs__dimens.xml b/com_actionbarsherlock/res/values-xlarge/abs__dimens.xml new file mode 100644 index 000000000..bfc535de1 --- /dev/null +++ b/com_actionbarsherlock/res/values-xlarge/abs__dimens.xml @@ -0,0 +1,45 @@ + + + + + 56dip + + 4dip + + 18dp + + 14dp + + -3dp + + 9dip + + + 64dip + + + 45% + + 72% + diff --git a/com_actionbarsherlock/res/values/abs__attrs.xml b/com_actionbarsherlock/res/values/abs__attrs.xml new file mode 100644 index 000000000..af022dc9c --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__attrs.xml @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/values/abs__bools.xml b/com_actionbarsherlock/res/values/abs__bools.xml new file mode 100644 index 000000000..0b432448d --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__bools.xml @@ -0,0 +1,22 @@ + + + + + false + true + true + + diff --git a/com_actionbarsherlock/res/values/abs__colors.xml b/com_actionbarsherlock/res/values/abs__colors.xml new file mode 100644 index 000000000..625c632ff --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__colors.xml @@ -0,0 +1,27 @@ + + + + + #ff000000 + #fff3f3f3 + @color/abs__background_holo_light + @color/abs__background_holo_dark + #ff4c4c4c + #ffb2b2b2 + @color/abs__bright_foreground_holo_light + @color/abs__bright_foreground_holo_dark + #ff33b5e5 + diff --git a/com_actionbarsherlock/res/values/abs__config.xml b/com_actionbarsherlock/res/values/abs__config.xml new file mode 100644 index 000000000..4c7b5d459 --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__config.xml @@ -0,0 +1,43 @@ + + + + + + + + 320dp + + + false + + + true + + + false + + diff --git a/com_actionbarsherlock/res/values/abs__dimens.xml b/com_actionbarsherlock/res/values/abs__dimens.xml new file mode 100644 index 000000000..0a409756c --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__dimens.xml @@ -0,0 +1,50 @@ + + + + + 48dip + + 8dip + + 18dp + + 14dp + + -3dp + + 5dip + + 2 + + + 56dip + + + 64dip + + + 65% + + 95% + diff --git a/com_actionbarsherlock/res/values/abs__ids.xml b/com_actionbarsherlock/res/values/abs__ids.xml new file mode 100644 index 000000000..f9f56045b --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__ids.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/com_actionbarsherlock/res/values/abs__strings.xml b/com_actionbarsherlock/res/values/abs__strings.xml new file mode 100644 index 000000000..c228281e7 --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__strings.xml @@ -0,0 +1,29 @@ + + + + + Navigate home + + Navigate up + + More options + + + Done + diff --git a/com_actionbarsherlock/res/values/abs__styles.xml b/com_actionbarsherlock/res/values/abs__styles.xml new file mode 100644 index 000000000..1d563c64f --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__styles.xml @@ -0,0 +1,344 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/res/values/abs__themes.xml b/com_actionbarsherlock/res/values/abs__themes.xml new file mode 100644 index 000000000..a7234ac8f --- /dev/null +++ b/com_actionbarsherlock/res/values/abs__themes.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/ActionBarSherlock.java b/com_actionbarsherlock/src/com/actionbarsherlock/ActionBarSherlock.java new file mode 100644 index 000000000..3dc3fd39e --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/ActionBarSherlock.java @@ -0,0 +1,778 @@ +package com.actionbarsherlock; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Iterator; +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.internal.ActionBarSherlockCompat; +import com.actionbarsherlock.internal.ActionBarSherlockNative; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +/** + *

Helper for implementing the action bar design pattern across all versions + * of Android.

+ * + *

This class will manage interaction with a custom action bar based on the + * Android 4.0 source code. The exposed API mirrors that of its native + * counterpart and you should refer to its documentation for instruction.

+ * + * @author Jake Wharton + * @version 4.0.0 + */ +public abstract class ActionBarSherlock { + protected static final String TAG = "ActionBarSherlock"; + protected static final boolean DEBUG = false; + + private static final Class[] CONSTRUCTOR_ARGS = new Class[] { Activity.class, int.class }; + private static final HashMap> IMPLEMENTATIONS = + new HashMap>(); + + static { + //Register our two built-in implementations + registerImplementation(ActionBarSherlockCompat.class); + registerImplementation(ActionBarSherlockNative.class); + } + + + /** + *

Denotes an implementation of ActionBarSherlock which provides an + * action bar-enhanced experience.

+ */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public @interface Implementation { + static final int DEFAULT_API = -1; + static final int DEFAULT_DPI = -1; + + int api() default DEFAULT_API; + int dpi() default DEFAULT_DPI; + } + + + /** Activity interface for menu creation callback. */ + public interface OnCreatePanelMenuListener { + public boolean onCreatePanelMenu(int featureId, Menu menu); + } + /** Activity interface for menu creation callback. */ + public interface OnCreateOptionsMenuListener { + public boolean onCreateOptionsMenu(Menu menu); + } + /** Activity interface for menu item selection callback. */ + public interface OnMenuItemSelectedListener { + public boolean onMenuItemSelected(int featureId, MenuItem item); + } + /** Activity interface for menu item selection callback. */ + public interface OnOptionsItemSelectedListener { + public boolean onOptionsItemSelected(MenuItem item); + } + /** Activity interface for menu preparation callback. */ + public interface OnPreparePanelListener { + public boolean onPreparePanel(int featureId, View view, Menu menu); + } + /** Activity interface for menu preparation callback. */ + public interface OnPrepareOptionsMenuListener { + public boolean onPrepareOptionsMenu(Menu menu); + } + /** Activity interface for action mode finished callback. */ + public interface OnActionModeFinishedListener { + public void onActionModeFinished(ActionMode mode); + } + /** Activity interface for action mode started callback. */ + public interface OnActionModeStartedListener { + public void onActionModeStarted(ActionMode mode); + } + + + /** + * If set, the logic in these classes will assume that an {@link Activity} + * is dispatching all of the required events to the class. This flag should + * only be used internally or if you are creating your own base activity + * modeled after one of the included types (e.g., {@code SherlockActivity}). + */ + public static final int FLAG_DELEGATE = 1; + + + /** + * Register an ActionBarSherlock implementation. + * + * @param implementationClass Target implementation class which extends + * {@link ActionBarSherlock}. This class must also be annotated with + * {@link Implementation}. + */ + public static void registerImplementation(Class implementationClass) { + if (!implementationClass.isAnnotationPresent(Implementation.class)) { + throw new IllegalArgumentException("Class " + implementationClass.getSimpleName() + " is not annotated with @Implementation"); + } else if (IMPLEMENTATIONS.containsValue(implementationClass)) { + if (DEBUG) Log.w(TAG, "Class " + implementationClass.getSimpleName() + " already registered"); + return; + } + + Implementation impl = implementationClass.getAnnotation(Implementation.class); + if (DEBUG) Log.i(TAG, "Registering " + implementationClass.getSimpleName() + " with qualifier " + impl); + IMPLEMENTATIONS.put(impl, implementationClass); + } + + /** + * Unregister an ActionBarSherlock implementation. This should be + * considered very volatile and you should only use it if you know what + * you are doing. You have been warned. + * + * @param implementationClass Target implementation class. + * @return Boolean indicating whether the class was removed. + */ + public static boolean unregisterImplementation(Class implementationClass) { + return IMPLEMENTATIONS.values().remove(implementationClass); + } + + /** + * Wrap an activity with an action bar abstraction which will enable the + * use of a custom implementation on platforms where a native version does + * not exist. + * + * @param activity Activity to wrap. + * @return Instance to interact with the action bar. + */ + public static ActionBarSherlock wrap(Activity activity) { + return wrap(activity, 0); + } + + /** + * Wrap an activity with an action bar abstraction which will enable the + * use of a custom implementation on platforms where a native version does + * not exist. + * + * @param activity Owning activity. + * @param flags Option flags to control behavior. + * @return Instance to interact with the action bar. + */ + public static ActionBarSherlock wrap(Activity activity, int flags) { + //Create a local implementation map we can modify + HashMap> impls = + new HashMap>(IMPLEMENTATIONS); + boolean hasQualfier; + + /* DPI FILTERING */ + hasQualfier = false; + for (Implementation key : impls.keySet()) { + //Only honor TVDPI as a specific qualifier + if (key.dpi() == DisplayMetrics.DENSITY_TV) { + hasQualfier = true; + break; + } + } + if (hasQualfier) { + final boolean isTvDpi = activity.getResources().getDisplayMetrics().densityDpi == DisplayMetrics.DENSITY_TV; + for (Iterator keys = impls.keySet().iterator(); keys.hasNext(); ) { + int keyDpi = keys.next().dpi(); + if ((isTvDpi && keyDpi != DisplayMetrics.DENSITY_TV) + || (!isTvDpi && keyDpi == DisplayMetrics.DENSITY_TV)) { + keys.remove(); + } + } + } + + /* API FILTERING */ + hasQualfier = false; + for (Implementation key : impls.keySet()) { + if (key.api() != Implementation.DEFAULT_API) { + hasQualfier = true; + break; + } + } + if (hasQualfier) { + final int runtimeApi = Build.VERSION.SDK_INT; + int bestApi = 0; + for (Iterator keys = impls.keySet().iterator(); keys.hasNext(); ) { + int keyApi = keys.next().api(); + if (keyApi > runtimeApi) { + keys.remove(); + } else if (keyApi > bestApi) { + bestApi = keyApi; + } + } + for (Iterator keys = impls.keySet().iterator(); keys.hasNext(); ) { + if (keys.next().api() != bestApi) { + keys.remove(); + } + } + } + + if (impls.size() > 1) { + throw new IllegalStateException("More than one implementation matches configuration."); + } + if (impls.isEmpty()) { + throw new IllegalStateException("No implementations match configuration."); + } + Class impl = impls.values().iterator().next(); + if (DEBUG) Log.i(TAG, "Using implementation: " + impl.getSimpleName()); + + try { + Constructor ctor = impl.getConstructor(CONSTRUCTOR_ARGS); + return ctor.newInstance(activity, flags); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + /** Activity which is displaying the action bar. Also used for context. */ + protected final Activity mActivity; + /** Whether delegating actions for the activity or managing ourselves. */ + protected final boolean mIsDelegate; + + /** Reference to our custom menu inflater which supports action items. */ + protected MenuInflater mMenuInflater; + + + + protected ActionBarSherlock(Activity activity, int flags) { + if (DEBUG) Log.d(TAG, "[] activity: " + activity + ", flags: " + flags); + + mActivity = activity; + mIsDelegate = (flags & FLAG_DELEGATE) != 0; + } + + + /** + * Get the current action bar instance. + * + * @return Action bar instance. + */ + public abstract ActionBar getActionBar(); + + + /////////////////////////////////////////////////////////////////////////// + // Lifecycle and interaction callbacks when delegating + /////////////////////////////////////////////////////////////////////////// + + /** + * Notify action bar of a configuration change event. Should be dispatched + * after the call to the superclass implementation. + * + *
+     * @Override
+     * public void onConfigurationChanged(Configuration newConfig) {
+     *     super.onConfigurationChanged(newConfig);
+     *     mSherlock.dispatchConfigurationChanged(newConfig);
+     * }
+     * 
+ * + * @param newConfig The new device configuration. + */ + public void dispatchConfigurationChanged(Configuration newConfig) {} + + /** + * Notify the action bar that the activity has finished its resuming. This + * should be dispatched after the call to the superclass implementation. + * + *
+     * @Override
+     * protected void onPostResume() {
+     *     super.onPostResume();
+     *     mSherlock.dispatchPostResume();
+     * }
+     * 
+ */ + public void dispatchPostResume() {} + + /** + * Notify the action bar that the activity is pausing. This should be + * dispatched before the call to the superclass implementation. + * + *
+     * @Override
+     * protected void onPause() {
+     *     mSherlock.dispatchPause();
+     *     super.onPause();
+     * }
+     * 
+ */ + public void dispatchPause() {} + + /** + * Notify the action bar that the activity is stopping. This should be + * called before the superclass implementation. + * + *

+ * @Override + * protected void onStop() { + * mSherlock.dispatchStop(); + * super.onStop(); + * } + *

+ */ + public void dispatchStop() {} + + /** + * Indicate that the menu should be recreated by calling + * {@link OnCreateOptionsMenuListener#onCreateOptionsMenu(com.actionbarsherlock.view.Menu)}. + */ + public abstract void dispatchInvalidateOptionsMenu(); + + /** + * Notify the action bar that it should display its overflow menu if it is + * appropriate for the device. The implementation should conditionally + * call the superclass method only if this method returns {@code false}. + * + *

+ * @Override + * public void openOptionsMenu() { + * if (!mSherlock.dispatchOpenOptionsMenu()) { + * super.openOptionsMenu(); + * } + * } + *

+ * + * @return {@code true} if the opening of the menu was handled internally. + */ + public boolean dispatchOpenOptionsMenu() { + return false; + } + + /** + * Notify the action bar that it should close its overflow menu if it is + * appropriate for the device. This implementation should conditionally + * call the superclass method only if this method returns {@code false}. + * + *
+     * @Override
+     * public void closeOptionsMenu() {
+     *     if (!mSherlock.dispatchCloseOptionsMenu()) {
+     *         super.closeOptionsMenu();
+     *     }
+     * }
+     * 
+ * + * @return {@code true} if the closing of the menu was handled internally. + */ + public boolean dispatchCloseOptionsMenu() { + return false; + } + + /** + * Notify the class that the activity has finished its creation. This + * should be called after the superclass implementation. + * + *
+     * @Override
+     * protected void onPostCreate(Bundle savedInstanceState) {
+     *     mSherlock.dispatchPostCreate(savedInstanceState);
+     *     super.onPostCreate(savedInstanceState);
+     * }
+     * 
+ * + * @param savedInstanceState If the activity is being re-initialized after + * previously being shut down then this Bundle + * contains the data it most recently supplied in + * {@link Activity#}onSaveInstanceState(Bundle)}. + * Note: Otherwise it is null. + */ + public void dispatchPostCreate(Bundle savedInstanceState) {} + + /** + * Notify the action bar that the title has changed and the action bar + * should be updated to reflect the change. This should be called before + * the superclass implementation. + * + *
+     *  @Override
+     *  protected void onTitleChanged(CharSequence title, int color) {
+     *      mSherlock.dispatchTitleChanged(title, color);
+     *      super.onTitleChanged(title, color);
+     *  }
+     * 
+ * + * @param title New activity title. + * @param color New activity color. + */ + public void dispatchTitleChanged(CharSequence title, int color) {} + + /** + * Notify the action bar the user has created a key event. This is used to + * toggle the display of the overflow action item with the menu key and to + * close the action mode or expanded action item with the back key. + * + *
+     * @Override
+     * public boolean dispatchKeyEvent(KeyEvent event) {
+     *     if (mSherlock.dispatchKeyEvent(event)) {
+     *         return true;
+     *     }
+     *     return super.dispatchKeyEvent(event);
+     * }
+     * 
+ * + * @param event Description of the key event. + * @return {@code true} if the event was handled. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + return false; + } + + /** + * Notify the action bar that the Activity has triggered a menu creation + * which should happen on the conclusion of {@link Activity#onCreate}. This + * will be used to gain a reference to the native menu for native and + * overflow binding as well as to indicate when compatibility create should + * occur for the first time. + * + * @param menu Activity native menu. + * @return {@code true} since we always want to say that we have a native + */ + public abstract boolean dispatchCreateOptionsMenu(android.view.Menu menu); + + /** + * Notify the action bar that the Activity has triggered a menu preparation + * which usually means that the user has requested the overflow menu via a + * hardware menu key. You should return the result of this method call and + * not call the superclass implementation. + * + *

+ * @Override + * public final boolean onPrepareOptionsMenu(android.view.Menu menu) { + * return mSherlock.dispatchPrepareOptionsMenu(menu); + * } + *

+ * + * @param menu Activity native menu. + * @return {@code true} if menu display should proceed. + */ + public abstract boolean dispatchPrepareOptionsMenu(android.view.Menu menu); + + /** + * Notify the action bar that a native options menu item has been selected. + * The implementation should return the result of this method call. + * + *

+ * @Override + * public final boolean onOptionsItemSelected(android.view.MenuItem item) { + * return mSherlock.dispatchOptionsItemSelected(item); + * } + *

+ * + * @param item Options menu item. + * @return @{code true} if the selection was handled. + */ + public abstract boolean dispatchOptionsItemSelected(android.view.MenuItem item); + + /** + * Notify the action bar that the overflow menu has been opened. The + * implementation should conditionally return {@code true} if this method + * returns {@code true}, otherwise return the result of the superclass + * method. + * + *

+ * @Override + * public final boolean onMenuOpened(int featureId, android.view.Menu menu) { + * if (mSherlock.dispatchMenuOpened(featureId, menu)) { + * return true; + * } + * return super.onMenuOpened(featureId, menu); + * } + *

+ * + * @param featureId Window feature which triggered the event. + * @param menu Activity native menu. + * @return {@code true} if the event was handled by this method. + */ + public boolean dispatchMenuOpened(int featureId, android.view.Menu menu) { + return false; + } + + /** + * Notify the action bar that the overflow menu has been closed. This + * method should be called before the superclass implementation. + * + *

+ * @Override + * public void onPanelClosed(int featureId, android.view.Menu menu) { + * mSherlock.dispatchPanelClosed(featureId, menu); + * super.onPanelClosed(featureId, menu); + * } + *

+ * + * @param featureId + * @param menu + */ + public void dispatchPanelClosed(int featureId, android.view.Menu menu) {} + + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + + /** + * Internal method to trigger the menu creation process. + * + * @return {@code true} if menu creation should proceed. + */ + protected final boolean callbackCreateOptionsMenu(Menu menu) { + if (DEBUG) Log.d(TAG, "[callbackCreateOptionsMenu] menu: " + menu); + + boolean result = true; + if (mActivity instanceof OnCreatePanelMenuListener) { + OnCreatePanelMenuListener listener = (OnCreatePanelMenuListener)mActivity; + result = listener.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu); + } else if (mActivity instanceof OnCreateOptionsMenuListener) { + OnCreateOptionsMenuListener listener = (OnCreateOptionsMenuListener)mActivity; + result = listener.onCreateOptionsMenu(menu); + } + + if (DEBUG) Log.d(TAG, "[callbackCreateOptionsMenu] returning " + result); + return result; + } + + /** + * Internal method to trigger the menu preparation process. + * + * @return {@code true} if menu preparation should proceed. + */ + protected final boolean callbackPrepareOptionsMenu(Menu menu) { + if (DEBUG) Log.d(TAG, "[callbackPrepareOptionsMenu] menu: " + menu); + + boolean result = true; + if (mActivity instanceof OnPreparePanelListener) { + OnPreparePanelListener listener = (OnPreparePanelListener)mActivity; + result = listener.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu); + } else if (mActivity instanceof OnPrepareOptionsMenuListener) { + OnPrepareOptionsMenuListener listener = (OnPrepareOptionsMenuListener)mActivity; + result = listener.onPrepareOptionsMenu(menu); + } + + if (DEBUG) Log.d(TAG, "[callbackPrepareOptionsMenu] returning " + result); + return result; + } + + /** + * Internal method for dispatching options menu selection to the owning + * activity callback. + * + * @param item Selected options menu item. + * @return {@code true} if the item selection was handled in the callback. + */ + protected final boolean callbackOptionsItemSelected(MenuItem item) { + if (DEBUG) Log.d(TAG, "[callbackOptionsItemSelected] item: " + item.getTitleCondensed()); + + boolean result = false; + if (mActivity instanceof OnMenuItemSelectedListener) { + OnMenuItemSelectedListener listener = (OnMenuItemSelectedListener)mActivity; + result = listener.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); + } else if (mActivity instanceof OnOptionsItemSelectedListener) { + OnOptionsItemSelectedListener listener = (OnOptionsItemSelectedListener)mActivity; + result = listener.onOptionsItemSelected(item); + } + + if (DEBUG) Log.d(TAG, "[callbackOptionsItemSelected] returning " + result); + return result; + } + + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + + /** + * Query for the availability of a certain feature. + * + * @param featureId The feature ID to check. + * @return {@code true} if feature is enabled, {@code false} otherwise. + */ + public abstract boolean hasFeature(int featureId); + + /** + * Enable extended screen features. This must be called before + * {@code setContentView()}. May be called as many times as desired as long + * as it is before {@code setContentView()}. If not called, no extended + * features will be available. You can not turn off a feature once it is + * requested. + * + * @param featureId The desired features, defined as constants by Window. + * @return Returns true if the requested feature is supported and now + * enabled. + */ + public abstract boolean requestFeature(int featureId); + + /** + * Set extra options that will influence the UI for this window. + * + * @param uiOptions Flags specifying extra options for this window. + */ + public abstract void setUiOptions(int uiOptions); + + /** + * Set extra options that will influence the UI for this window. Only the + * bits filtered by mask will be modified. + * + * @param uiOptions Flags specifying extra options for this window. + * @param mask Flags specifying which options should be modified. Others + * will remain unchanged. + */ + public abstract void setUiOptions(int uiOptions, int mask); + + /** + * Set the content of the activity inside the action bar. + * + * @param layoutResId Layout resource ID. + */ + public abstract void setContentView(int layoutResId); + + /** + * Set the content of the activity inside the action bar. + * + * @param view The desired content to display. + */ + public void setContentView(View view) { + if (DEBUG) Log.d(TAG, "[setContentView] view: " + view); + + setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } + + /** + * Set the content of the activity inside the action bar. + * + * @param view The desired content to display. + * @param params Layout parameters to apply to the view. + */ + public abstract void setContentView(View view, ViewGroup.LayoutParams params); + + /** + * Variation on {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)} + * to add an additional content view to the screen. Added after any + * existing ones on the screen -- existing views are NOT removed. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public abstract void addContentView(View view, ViewGroup.LayoutParams params); + + /** + * Change the title associated with this activity. + */ + public abstract void setTitle(CharSequence title); + + /** + * Change the title associated with this activity. + */ + public void setTitle(int resId) { + if (DEBUG) Log.d(TAG, "[setTitle] resId: " + resId); + + setTitle(mActivity.getString(resId)); + } + + /** + * Sets the visibility of the progress bar in the title. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param visible Whether to show the progress bars in the title. + */ + public abstract void setProgressBarVisibility(boolean visible); + + /** + * Sets the visibility of the indeterminate progress bar in the title. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param visible Whether to show the progress bars in the title. + */ + public abstract void setProgressBarIndeterminateVisibility(boolean visible); + + /** + * Sets whether the horizontal progress bar in the title should be indeterminate (the circular + * is always indeterminate). + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param indeterminate Whether the horizontal progress bar should be indeterminate. + */ + public abstract void setProgressBarIndeterminate(boolean indeterminate); + + /** + * Sets the progress for the progress bars in the title. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param progress The progress for the progress bar. Valid ranges are from + * 0 to 10000 (both inclusive). If 10000 is given, the progress + * bar will be completely filled and will fade out. + */ + public abstract void setProgress(int progress); + + /** + * Sets the secondary progress for the progress bar in the title. This + * progress is drawn between the primary progress (set via + * {@link #setProgress(int)} and the background. It can be ideal for media + * scenarios such as showing the buffering progress while the default + * progress shows the play progress. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from + * 0 to 10000 (both inclusive). + */ + public abstract void setSecondaryProgress(int secondaryProgress); + + /** + * Get a menu inflater instance which supports the newer menu attributes. + * + * @return Menu inflater instance. + */ + public MenuInflater getMenuInflater() { + if (DEBUG) Log.d(TAG, "[getMenuInflater]"); + + // Make sure that action views can get an appropriate theme. + if (mMenuInflater == null) { + if (getActionBar() != null) { + mMenuInflater = new MenuInflater(getThemedContext()); + } else { + mMenuInflater = new MenuInflater(mActivity); + } + } + return mMenuInflater; + } + + protected abstract Context getThemedContext(); + + /** + * Start an action mode. + * + * @param callback Callback that will manage lifecycle events for this + * context mode. + * @return The ContextMode that was started, or null if it was canceled. + * @see ActionMode + */ + public abstract ActionMode startActionMode(ActionMode.Callback callback); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/ActionBar.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/ActionBar.java new file mode 100644 index 000000000..2497d24ff --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/ActionBar.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.app; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v4.app.FragmentTransaction; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.widget.SpinnerAdapter; + +/** + * A window feature at the top of the activity that may display the activity title, navigation + * modes, and other interactive items. + *

Beginning with Android 3.0 (API level 11), the action bar appears at the top of an + * activity's window when the activity uses the system's {@link + * android.R.style#Theme_Holo Holo} theme (or one of its descendant themes), which is the default. + * You may otherwise add the action bar by calling {@link + * android.view.Window#requestFeature requestFeature(FEATURE_ACTION_BAR)} or by declaring it in a + * custom theme with the {@link android.R.styleable#Theme_windowActionBar windowActionBar} property. + *

By default, the action bar shows the application icon on + * the left, followed by the activity title. If your activity has an options menu, you can make + * select items accessible directly from the action bar as "action items". You can also + * modify various characteristics of the action bar or remove it completely.

+ *

From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link + * android.app.Activity#getActionBar getActionBar()}.

+ *

In some cases, the action bar may be overlayed by another bar that enables contextual actions, + * using an {@link android.view.ActionMode}. For example, when the user selects one or more items in + * your activity, you can enable an action mode that offers actions specific to the selected + * items, with a UI that temporarily replaces the action bar. Although the UI may occupy the + * same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for + * {@link ActionBar}. + *

+ */ +public abstract class ActionBar { + /** + * Standard navigation mode. Consists of either a logo or icon + * and title text with an optional subtitle. Clicking any of these elements + * will dispatch onOptionsItemSelected to the host Activity with + * a MenuItem with item ID android.R.id.home. + */ + public static final int NAVIGATION_MODE_STANDARD = android.app.ActionBar.NAVIGATION_MODE_STANDARD; + + /** + * List navigation mode. Instead of static title text this mode + * presents a list menu for navigation within the activity. + * e.g. this might be presented to the user as a dropdown list. + */ + public static final int NAVIGATION_MODE_LIST = android.app.ActionBar.NAVIGATION_MODE_LIST; + + /** + * Tab navigation mode. Instead of static title text this mode + * presents a series of tabs for navigation within the activity. + */ + public static final int NAVIGATION_MODE_TABS = android.app.ActionBar.NAVIGATION_MODE_TABS; + + /** + * Use logo instead of icon if available. This flag will cause appropriate + * navigation modes to use a wider logo in place of the standard icon. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public static final int DISPLAY_USE_LOGO = android.app.ActionBar.DISPLAY_USE_LOGO; + + /** + * Show 'home' elements in this action bar, leaving more space for other + * navigation elements. This includes logo and icon. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public static final int DISPLAY_SHOW_HOME = android.app.ActionBar.DISPLAY_SHOW_HOME; + + /** + * Display the 'home' element such that it appears as an 'up' affordance. + * e.g. show an arrow to the left indicating the action that will be taken. + * + * Set this flag if selecting the 'home' button in the action bar to return + * up by a single level in your UI rather than back to the top level or front page. + * + *

Setting this option will implicitly enable interaction with the home/up + * button. See {@link #setHomeButtonEnabled(boolean)}. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public static final int DISPLAY_HOME_AS_UP = android.app.ActionBar.DISPLAY_HOME_AS_UP; + + /** + * Show the activity title and subtitle, if present. + * + * @see #setTitle(CharSequence) + * @see #setTitle(int) + * @see #setSubtitle(CharSequence) + * @see #setSubtitle(int) + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public static final int DISPLAY_SHOW_TITLE = android.app.ActionBar.DISPLAY_SHOW_TITLE; + + /** + * Show the custom view if one has been set. + * @see #setCustomView(View) + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public static final int DISPLAY_SHOW_CUSTOM = android.app.ActionBar.DISPLAY_SHOW_CUSTOM; + + /** + * Set the action bar into custom navigation mode, supplying a view + * for custom navigation. + * + * Custom navigation views appear between the application icon and + * any action buttons and may use any space available there. Common + * use cases for custom navigation views might include an auto-suggesting + * address bar for a browser or other navigation mechanisms that do not + * translate well to provided navigation modes. + * + * @param view Custom navigation view to place in the ActionBar. + */ + public abstract void setCustomView(View view); + + /** + * Set the action bar into custom navigation mode, supplying a view + * for custom navigation. + * + *

Custom navigation views appear between the application icon and + * any action buttons and may use any space available there. Common + * use cases for custom navigation views might include an auto-suggesting + * address bar for a browser or other navigation mechanisms that do not + * translate well to provided navigation modes.

+ * + *

The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for + * the custom view to be displayed.

+ * + * @param view Custom navigation view to place in the ActionBar. + * @param layoutParams How this custom view should layout in the bar. + * + * @see #setDisplayOptions(int, int) + */ + public abstract void setCustomView(View view, LayoutParams layoutParams); + + /** + * Set the action bar into custom navigation mode, supplying a view + * for custom navigation. + * + *

Custom navigation views appear between the application icon and + * any action buttons and may use any space available there. Common + * use cases for custom navigation views might include an auto-suggesting + * address bar for a browser or other navigation mechanisms that do not + * translate well to provided navigation modes.

+ * + *

The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for + * the custom view to be displayed.

+ * + * @param resId Resource ID of a layout to inflate into the ActionBar. + * + * @see #setDisplayOptions(int, int) + */ + public abstract void setCustomView(int resId); + + /** + * Set the icon to display in the 'home' section of the action bar. + * The action bar will use an icon specified by its style or the + * activity icon by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param resId Resource ID of a drawable to show as an icon. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setIcon(int resId); + + /** + * Set the icon to display in the 'home' section of the action bar. + * The action bar will use an icon specified by its style or the + * activity icon by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param icon Drawable to show as an icon. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setIcon(Drawable icon); + + /** + * Set the logo to display in the 'home' section of the action bar. + * The action bar will use a logo specified by its style or the + * activity logo by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param resId Resource ID of a drawable to show as a logo. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setLogo(int resId); + + /** + * Set the logo to display in the 'home' section of the action bar. + * The action bar will use a logo specified by its style or the + * activity logo by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param logo Drawable to show as a logo. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setLogo(Drawable logo); + + /** + * Set the adapter and navigation callback for list navigation mode. + * + * The supplied adapter will provide views for the expanded list as well as + * the currently selected item. (These may be displayed differently.) + * + * The supplied OnNavigationListener will alert the application when the user + * changes the current list selection. + * + * @param adapter An adapter that will provide views both to display + * the current navigation selection and populate views + * within the dropdown navigation menu. + * @param callback An OnNavigationListener that will receive events when the user + * selects a navigation item. + */ + public abstract void setListNavigationCallbacks(SpinnerAdapter adapter, + OnNavigationListener callback); + + /** + * Set the selected navigation item in list or tabbed navigation modes. + * + * @param position Position of the item to select. + */ + public abstract void setSelectedNavigationItem(int position); + + /** + * Get the position of the selected navigation item in list or tabbed navigation modes. + * + * @return Position of the selected item. + */ + public abstract int getSelectedNavigationIndex(); + + /** + * Get the number of navigation items present in the current navigation mode. + * + * @return Number of navigation items. + */ + public abstract int getNavigationItemCount(); + + /** + * Set the action bar's title. This will only be displayed if + * {@link #DISPLAY_SHOW_TITLE} is set. + * + * @param title Title to set + * + * @see #setTitle(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setTitle(CharSequence title); + + /** + * Set the action bar's title. This will only be displayed if + * {@link #DISPLAY_SHOW_TITLE} is set. + * + * @param resId Resource ID of title string to set + * + * @see #setTitle(CharSequence) + * @see #setDisplayOptions(int, int) + */ + public abstract void setTitle(int resId); + + /** + * Set the action bar's subtitle. This will only be displayed if + * {@link #DISPLAY_SHOW_TITLE} is set. Set to null to disable the + * subtitle entirely. + * + * @param subtitle Subtitle to set + * + * @see #setSubtitle(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setSubtitle(CharSequence subtitle); + + /** + * Set the action bar's subtitle. This will only be displayed if + * {@link #DISPLAY_SHOW_TITLE} is set. + * + * @param resId Resource ID of subtitle string to set + * + * @see #setSubtitle(CharSequence) + * @see #setDisplayOptions(int, int) + */ + public abstract void setSubtitle(int resId); + + /** + * Set display options. This changes all display option bits at once. To change + * a limited subset of display options, see {@link #setDisplayOptions(int, int)}. + * + * @param options A combination of the bits defined by the DISPLAY_ constants + * defined in ActionBar. + */ + public abstract void setDisplayOptions(int options); + + /** + * Set selected display options. Only the options specified by mask will be changed. + * To change all display option bits at once, see {@link #setDisplayOptions(int)}. + * + *

Example: setDisplayOptions(0, DISPLAY_SHOW_HOME) will disable the + * {@link #DISPLAY_SHOW_HOME} option. + * setDisplayOptions(DISPLAY_SHOW_HOME, DISPLAY_SHOW_HOME | DISPLAY_USE_LOGO) + * will enable {@link #DISPLAY_SHOW_HOME} and disable {@link #DISPLAY_USE_LOGO}. + * + * @param options A combination of the bits defined by the DISPLAY_ constants + * defined in ActionBar. + * @param mask A bit mask declaring which display options should be changed. + */ + public abstract void setDisplayOptions(int options, int mask); + + /** + * Set whether to display the activity logo rather than the activity icon. + * A logo is often a wider, more detailed image. + * + *

To set several display options at once, see the setDisplayOptions methods. + * + * @param useLogo true to use the activity logo, false to use the activity icon. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setDisplayUseLogoEnabled(boolean useLogo); + + /** + * Set whether to include the application home affordance in the action bar. + * Home is presented as either an activity icon or logo. + * + *

To set several display options at once, see the setDisplayOptions methods. + * + * @param showHome true to show home, false otherwise. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setDisplayShowHomeEnabled(boolean showHome); + + /** + * Set whether home should be displayed as an "up" affordance. + * Set this to true if selecting "home" returns up by a single level in your UI + * rather than back to the top level or front page. + * + *

To set several display options at once, see the setDisplayOptions methods. + * + * @param showHomeAsUp true to show the user that selecting home will return one + * level up rather than to the top level of the app. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setDisplayHomeAsUpEnabled(boolean showHomeAsUp); + + /** + * Set whether an activity title/subtitle should be displayed. + * + *

To set several display options at once, see the setDisplayOptions methods. + * + * @param showTitle true to display a title/subtitle if present. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setDisplayShowTitleEnabled(boolean showTitle); + + /** + * Set whether a custom view should be displayed, if set. + * + *

To set several display options at once, see the setDisplayOptions methods. + * + * @param showCustom true if the currently set custom view should be displayed, false otherwise. + * + * @see #setDisplayOptions(int) + * @see #setDisplayOptions(int, int) + */ + public abstract void setDisplayShowCustomEnabled(boolean showCustom); + + /** + * Set the ActionBar's background. This will be used for the primary + * action bar. + * + * @param d Background drawable + * @see #setStackedBackgroundDrawable(Drawable) + * @see #setSplitBackgroundDrawable(Drawable) + */ + public abstract void setBackgroundDrawable(Drawable d); + + /** + * Set the ActionBar's stacked background. This will appear + * in the second row/stacked bar on some devices and configurations. + * + * @param d Background drawable for the stacked row + */ + public void setStackedBackgroundDrawable(Drawable d) { } + + /** + * Set the ActionBar's split background. This will appear in + * the split action bar containing menu-provided action buttons + * on some devices and configurations. + *

You can enable split action bar with {@link android.R.attr#uiOptions} + * + * @param d Background drawable for the split bar + */ + public void setSplitBackgroundDrawable(Drawable d) { } + + /** + * @return The current custom view. + */ + public abstract View getCustomView(); + + /** + * Returns the current ActionBar title in standard mode. + * Returns null if {@link #getNavigationMode()} would not return + * {@link #NAVIGATION_MODE_STANDARD}. + * + * @return The current ActionBar title or null. + */ + public abstract CharSequence getTitle(); + + /** + * Returns the current ActionBar subtitle in standard mode. + * Returns null if {@link #getNavigationMode()} would not return + * {@link #NAVIGATION_MODE_STANDARD}. + * + * @return The current ActionBar subtitle or null. + */ + public abstract CharSequence getSubtitle(); + + /** + * Returns the current navigation mode. The result will be one of: + *

    + *
  • {@link #NAVIGATION_MODE_STANDARD}
  • + *
  • {@link #NAVIGATION_MODE_LIST}
  • + *
  • {@link #NAVIGATION_MODE_TABS}
  • + *
+ * + * @return The current navigation mode. + */ + public abstract int getNavigationMode(); + + /** + * Set the current navigation mode. + * + * @param mode The new mode to set. + * @see #NAVIGATION_MODE_STANDARD + * @see #NAVIGATION_MODE_LIST + * @see #NAVIGATION_MODE_TABS + */ + public abstract void setNavigationMode(int mode); + + /** + * @return The current set of display options. + */ + public abstract int getDisplayOptions(); + + /** + * Create and return a new {@link Tab}. + * This tab will not be included in the action bar until it is added. + * + *

Very often tabs will be used to switch between {@link Fragment} + * objects. Here is a typical implementation of such tabs:

+ * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java + * complete} + * + * @return A new Tab + * + * @see #addTab(Tab) + */ + public abstract Tab newTab(); + + /** + * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list. + * If this is the first tab to be added it will become the selected tab. + * + * @param tab Tab to add + */ + public abstract void addTab(Tab tab); + + /** + * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list. + * + * @param tab Tab to add + * @param setSelected True if the added tab should become the selected tab. + */ + public abstract void addTab(Tab tab, boolean setSelected); + + /** + * Add a tab for use in tabbed navigation mode. The tab will be inserted at + * position. If this is the first tab to be added it will become + * the selected tab. + * + * @param tab The tab to add + * @param position The new position of the tab + */ + public abstract void addTab(Tab tab, int position); + + /** + * Add a tab for use in tabbed navigation mode. The tab will be insterted at + * position. + * + * @param tab The tab to add + * @param position The new position of the tab + * @param setSelected True if the added tab should become the selected tab. + */ + public abstract void addTab(Tab tab, int position, boolean setSelected); + + /** + * Remove a tab from the action bar. If the removed tab was selected it will be deselected + * and another tab will be selected if present. + * + * @param tab The tab to remove + */ + public abstract void removeTab(Tab tab); + + /** + * Remove a tab from the action bar. If the removed tab was selected it will be deselected + * and another tab will be selected if present. + * + * @param position Position of the tab to remove + */ + public abstract void removeTabAt(int position); + + /** + * Remove all tabs from the action bar and deselect the current tab. + */ + public abstract void removeAllTabs(); + + /** + * Select the specified tab. If it is not a child of this action bar it will be added. + * + *

Note: If you want to select by index, use {@link #setSelectedNavigationItem(int)}.

+ * + * @param tab Tab to select + */ + public abstract void selectTab(Tab tab); + + /** + * Returns the currently selected tab if in tabbed navigation mode and there is at least + * one tab present. + * + * @return The currently selected tab or null + */ + public abstract Tab getSelectedTab(); + + /** + * Returns the tab at the specified index. + * + * @param index Index value in the range 0-get + * @return + */ + public abstract Tab getTabAt(int index); + + /** + * Returns the number of tabs currently registered with the action bar. + * @return Tab count + */ + public abstract int getTabCount(); + + /** + * Retrieve the current height of the ActionBar. + * + * @return The ActionBar's height + */ + public abstract int getHeight(); + + /** + * Show the ActionBar if it is not currently showing. + * If the window hosting the ActionBar does not have the feature + * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application + * content to fit the new space available. + */ + public abstract void show(); + + /** + * Hide the ActionBar if it is currently showing. + * If the window hosting the ActionBar does not have the feature + * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application + * content to fit the new space available. + */ + public abstract void hide(); + + /** + * @return true if the ActionBar is showing, false otherwise. + */ + public abstract boolean isShowing(); + + /** + * Add a listener that will respond to menu visibility change events. + * + * @param listener The new listener to add + */ + public abstract void addOnMenuVisibilityListener(OnMenuVisibilityListener listener); + + /** + * Remove a menu visibility listener. This listener will no longer receive menu + * visibility change events. + * + * @param listener A listener to remove that was previously added + */ + public abstract void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener); + + /** + * Enable or disable the "home" button in the corner of the action bar. (Note that this + * is the application home/up affordance on the action bar, not the systemwide home + * button.) + * + *

This defaults to true for packages targeting < API 14. For packages targeting + * API 14 or greater, the application should call this method to enable interaction + * with the home/up affordance. + * + *

Setting the {@link #DISPLAY_HOME_AS_UP} display option will automatically enable + * the home button. + * + * @param enabled true to enable the home button, false to disable the home button. + */ + public void setHomeButtonEnabled(boolean enabled) { } + + /** + * Returns a {@link Context} with an appropriate theme for creating views that + * will appear in the action bar. If you are inflating or instantiating custom views + * that will appear in an action bar, you should use the Context returned by this method. + * (This includes adapters used for list navigation mode.) + * This will ensure that views contrast properly against the action bar. + * + * @return A themed Context for creating views + */ + public Context getThemedContext() { return null; } + + /** + * Listener interface for ActionBar navigation events. + */ + public interface OnNavigationListener { + /** + * This method is called whenever a navigation item in your action bar + * is selected. + * + * @param itemPosition Position of the item clicked. + * @param itemId ID of the item clicked. + * @return True if the event was handled, false otherwise. + */ + public boolean onNavigationItemSelected(int itemPosition, long itemId); + } + + /** + * Listener for receiving events when action bar menus are shown or hidden. + */ + public interface OnMenuVisibilityListener { + /** + * Called when an action bar menu is shown or hidden. Applications may want to use + * this to tune auto-hiding behavior for the action bar or pause/resume video playback, + * gameplay, or other activity within the main content area. + * + * @param isVisible True if an action bar menu is now visible, false if no action bar + * menus are visible. + */ + public void onMenuVisibilityChanged(boolean isVisible); + } + + /** + * A tab in the action bar. + * + *

Tabs manage the hiding and showing of {@link Fragment}s. + */ + public static abstract class Tab { + /** + * An invalid position for a tab. + * + * @see #getPosition() + */ + public static final int INVALID_POSITION = -1; + + /** + * Return the current position of this tab in the action bar. + * + * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in + * the action bar. + */ + public abstract int getPosition(); + + /** + * Return the icon associated with this tab. + * + * @return The tab's icon + */ + public abstract Drawable getIcon(); + + /** + * Return the text of this tab. + * + * @return The tab's text + */ + public abstract CharSequence getText(); + + /** + * Set the icon displayed on this tab. + * + * @param icon The drawable to use as an icon + * @return The current instance for call chaining + */ + public abstract Tab setIcon(Drawable icon); + + /** + * Set the icon displayed on this tab. + * + * @param resId Resource ID referring to the drawable to use as an icon + * @return The current instance for call chaining + */ + public abstract Tab setIcon(int resId); + + /** + * Set the text displayed on this tab. Text may be truncated if there is not + * room to display the entire string. + * + * @param text The text to display + * @return The current instance for call chaining + */ + public abstract Tab setText(CharSequence text); + + /** + * Set the text displayed on this tab. Text may be truncated if there is not + * room to display the entire string. + * + * @param resId A resource ID referring to the text that should be displayed + * @return The current instance for call chaining + */ + public abstract Tab setText(int resId); + + /** + * Set a custom view to be used for this tab. This overrides values set by + * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}. + * + * @param view Custom view to be used as a tab. + * @return The current instance for call chaining + */ + public abstract Tab setCustomView(View view); + + /** + * Set a custom view to be used for this tab. This overrides values set by + * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}. + * + * @param layoutResId A layout resource to inflate and use as a custom tab view + * @return The current instance for call chaining + */ + public abstract Tab setCustomView(int layoutResId); + + /** + * Retrieve a previously set custom view for this tab. + * + * @return The custom view set by {@link #setCustomView(View)}. + */ + public abstract View getCustomView(); + + /** + * Give this Tab an arbitrary object to hold for later use. + * + * @param obj Object to store + * @return The current instance for call chaining + */ + public abstract Tab setTag(Object obj); + + /** + * @return This Tab's tag object. + */ + public abstract Object getTag(); + + /** + * Set the {@link TabListener} that will handle switching to and from this tab. + * All tabs must have a TabListener set before being added to the ActionBar. + * + * @param listener Listener to handle tab selection events + * @return The current instance for call chaining + */ + public abstract Tab setTabListener(TabListener listener); + + /** + * Select this tab. Only valid if the tab has been added to the action bar. + */ + public abstract void select(); + + /** + * Set a description of this tab's content for use in accessibility support. + * If no content description is provided the title will be used. + * + * @param resId A resource ID referring to the description text + * @return The current instance for call chaining + * @see #setContentDescription(CharSequence) + * @see #getContentDescription() + */ + public abstract Tab setContentDescription(int resId); + + /** + * Set a description of this tab's content for use in accessibility support. + * If no content description is provided the title will be used. + * + * @param contentDesc Description of this tab's content + * @return The current instance for call chaining + * @see #setContentDescription(int) + * @see #getContentDescription() + */ + public abstract Tab setContentDescription(CharSequence contentDesc); + + /** + * Gets a brief description of this tab's content for use in accessibility support. + * + * @return Description of this tab's content + * @see #setContentDescription(CharSequence) + * @see #setContentDescription(int) + */ + public abstract CharSequence getContentDescription(); + } + + /** + * Callback interface invoked when a tab is focused, unfocused, added, or removed. + */ + public interface TabListener { + /** + * Called when a tab enters the selected state. + * + * @param tab The tab that was selected + * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute + * during a tab switch. The previous tab's unselect and this tab's select will be + * executed in a single transaction. This FragmentTransaction does not support + * being added to the back stack. + */ + public void onTabSelected(Tab tab, FragmentTransaction ft); + + /** + * Called when a tab exits the selected state. + * + * @param tab The tab that was unselected + * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute + * during a tab switch. This tab's unselect and the newly selected tab's select + * will be executed in a single transaction. This FragmentTransaction does not + * support being added to the back stack. + */ + public void onTabUnselected(Tab tab, FragmentTransaction ft); + + /** + * Called when a tab that is already selected is chosen again by the user. + * Some applications may use this action to return to the top level of a category. + * + * @param tab The tab that was reselected. + * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute + * once this method returns. This FragmentTransaction does not support + * being added to the back stack. + */ + public void onTabReselected(Tab tab, FragmentTransaction ft); + } + + /** + * Per-child layout information associated with action bar custom views. + * + * @attr ref android.R.styleable#ActionBar_LayoutParams_layout_gravity + */ + public static class LayoutParams extends MarginLayoutParams { + /** + * Gravity for the view associated with these LayoutParams. + * + * @see android.view.Gravity + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = -1, to = "NONE"), + @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), + @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), + @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), + @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"), + @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"), + @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"), + @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"), + @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), + @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"), + @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") + }) + public int gravity = -1; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int width, int height) { + super(width, height); + this.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT; + } + + public LayoutParams(int width, int height, int gravity) { + super(width, height); + this.gravity = gravity; + } + + public LayoutParams(int gravity) { + this(WRAP_CONTENT, FILL_PARENT, gravity); + } + + public LayoutParams(LayoutParams source) { + super(source); + + this.gravity = source.gravity; + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockActivity.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockActivity.java new file mode 100644 index 000000000..d0a6d8128 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockActivity.java @@ -0,0 +1,253 @@ +package com.actionbarsherlock.app; + +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.view.ViewGroup.LayoutParams; +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeFinishedListener; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeStartedListener; +import com.actionbarsherlock.ActionBarSherlock.OnCreatePanelMenuListener; +import com.actionbarsherlock.ActionBarSherlock.OnMenuItemSelectedListener; +import com.actionbarsherlock.ActionBarSherlock.OnPreparePanelListener; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public abstract class SherlockActivity extends Activity implements OnCreatePanelMenuListener, OnPreparePanelListener, OnMenuItemSelectedListener, OnActionModeStartedListener, OnActionModeFinishedListener { + private ActionBarSherlock mSherlock; + + protected final ActionBarSherlock getSherlock() { + if (mSherlock == null) { + mSherlock = ActionBarSherlock.wrap(this, ActionBarSherlock.FLAG_DELEGATE); + } + return mSherlock; + } + + + /////////////////////////////////////////////////////////////////////////// + // Action bar and mode + /////////////////////////////////////////////////////////////////////////// + + public ActionBar getSupportActionBar() { + return getSherlock().getActionBar(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + return getSherlock().startActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + + + /////////////////////////////////////////////////////////////////////////// + // General lifecycle/callback dispatching + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getSherlock().dispatchConfigurationChanged(newConfig); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getSherlock().dispatchPostResume(); + } + + @Override + protected void onPause() { + getSherlock().dispatchPause(); + super.onPause(); + } + + @Override + protected void onStop() { + getSherlock().dispatchStop(); + super.onStop(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + getSherlock().dispatchPostCreate(savedInstanceState); + super.onPostCreate(savedInstanceState); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + getSherlock().dispatchTitleChanged(title, color); + super.onTitleChanged(title, color); + } + + @Override + public final boolean onMenuOpened(int featureId, android.view.Menu menu) { + if (getSherlock().dispatchMenuOpened(featureId, menu)) { + return true; + } + return super.onMenuOpened(featureId, menu); + } + + @Override + public void onPanelClosed(int featureId, android.view.Menu menu) { + getSherlock().dispatchPanelClosed(featureId, menu); + super.onPanelClosed(featureId, menu); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (getSherlock().dispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + + /////////////////////////////////////////////////////////////////////////// + // Native menu handling + /////////////////////////////////////////////////////////////////////////// + + public MenuInflater getSupportMenuInflater() { + return getSherlock().getMenuInflater(); + } + + public void invalidateOptionsMenu() { + getSherlock().dispatchInvalidateOptionsMenu(); + } + + public void supportInvalidateOptionsMenu() { + invalidateOptionsMenu(); + } + + @Override + public final boolean onCreateOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchCreateOptionsMenu(menu); + } + + @Override + public final boolean onPrepareOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchPrepareOptionsMenu(menu); + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + return getSherlock().dispatchOptionsItemSelected(item); + } + + @Override + public void openOptionsMenu() { + if (!getSherlock().dispatchOpenOptionsMenu()) { + super.openOptionsMenu(); + } + } + + @Override + public void closeOptionsMenu() { + if (!getSherlock().dispatchCloseOptionsMenu()) { + super.closeOptionsMenu(); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // Sherlock menu handling + /////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onCreateOptionsMenu(menu); + } + return false; + } + + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onPrepareOptionsMenu(menu); + } + return false; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onOptionsItemSelected(item); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + + /////////////////////////////////////////////////////////////////////////// + // Content + /////////////////////////////////////////////////////////////////////////// + + @Override + public void addContentView(View view, LayoutParams params) { + getSherlock().addContentView(view, params); + } + + @Override + public void setContentView(int layoutResId) { + getSherlock().setContentView(layoutResId); + } + + @Override + public void setContentView(View view, LayoutParams params) { + getSherlock().setContentView(view, params); + } + + @Override + public void setContentView(View view) { + getSherlock().setContentView(view); + } + + public void requestWindowFeature(long featureId) { + getSherlock().requestFeature((int)featureId); + } + + + /////////////////////////////////////////////////////////////////////////// + // Progress Indication + /////////////////////////////////////////////////////////////////////////// + + public void setSupportProgress(int progress) { + getSherlock().setProgress(progress); + } + + public void setSupportProgressBarIndeterminate(boolean indeterminate) { + getSherlock().setProgressBarIndeterminate(indeterminate); + } + + public void setSupportProgressBarIndeterminateVisibility(boolean visible) { + getSherlock().setProgressBarIndeterminateVisibility(visible); + } + + public void setSupportProgressBarVisibility(boolean visible) { + getSherlock().setProgressBarVisibility(visible); + } + + public void setSupportSecondaryProgress(int secondaryProgress) { + getSherlock().setSecondaryProgress(secondaryProgress); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockDialogFragment.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockDialogFragment.java new file mode 100644 index 000000000..0ec5833c3 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockDialogFragment.java @@ -0,0 +1,72 @@ +package com.actionbarsherlock.app; + +import static com.actionbarsherlock.app.SherlockFragmentActivity.DEBUG; +import android.app.Activity; +import android.support.v4.app.DialogFragment; +import android.util.Log; +import com.actionbarsherlock.internal.view.menu.MenuItemMule; +import com.actionbarsherlock.internal.view.menu.MenuMule; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public class SherlockDialogFragment extends DialogFragment { + private static final String TAG = "SherlockDialogFragment"; + + private SherlockFragmentActivity mActivity; + + public SherlockFragmentActivity getSherlockActivity() { + return mActivity; + } + + @Override + public void onAttach(Activity activity) { + if (!(activity instanceof SherlockFragmentActivity)) { + throw new IllegalStateException(TAG + " must be attached to a SherlockFragmentActivity."); + } + mActivity = (SherlockFragmentActivity)activity; + + super.onAttach(activity); + } + + @Override + public final void onCreateOptionsMenu(android.view.Menu menu, android.view.MenuInflater inflater) { + if (DEBUG) Log.d(TAG, "[onCreateOptionsMenu] menu: " + menu + ", inflater: " + inflater); + + if (menu instanceof MenuMule) { + onCreateOptionsMenu(((MenuMule)menu).unwrap(), mActivity.getSupportMenuInflater()); + } + } + + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + //Nothing to see here. + } + + @Override + public final void onPrepareOptionsMenu(android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[onPrepareOptionsMenu] menu: " + menu); + + if (menu instanceof MenuMule) { + onPrepareOptionsMenu(((MenuMule)menu).unwrap()); + } + } + + public void onPrepareOptionsMenu(Menu menu) { + //Nothing to see here. + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + if (DEBUG) Log.d(TAG, "[onOptionsItemSelected] item: " + item); + + if (item instanceof MenuItemMule) { + return onOptionsItemSelected(((MenuItemMule)item).unwrap()); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + //Nothing to see here. + return false; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockExpandableListActivity.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockExpandableListActivity.java new file mode 100644 index 000000000..3247c987c --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockExpandableListActivity.java @@ -0,0 +1,253 @@ +package com.actionbarsherlock.app; + +import android.app.ExpandableListActivity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.Window; +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeFinishedListener; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeStartedListener; +import com.actionbarsherlock.ActionBarSherlock.OnCreatePanelMenuListener; +import com.actionbarsherlock.ActionBarSherlock.OnMenuItemSelectedListener; +import com.actionbarsherlock.ActionBarSherlock.OnPreparePanelListener; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public abstract class SherlockExpandableListActivity extends ExpandableListActivity implements OnCreatePanelMenuListener, OnPreparePanelListener, OnMenuItemSelectedListener, OnActionModeStartedListener, OnActionModeFinishedListener { + private ActionBarSherlock mSherlock; + + protected final ActionBarSherlock getSherlock() { + if (mSherlock == null) { + mSherlock = ActionBarSherlock.wrap(this, ActionBarSherlock.FLAG_DELEGATE); + } + return mSherlock; + } + + + /////////////////////////////////////////////////////////////////////////// + // Action bar and mode + /////////////////////////////////////////////////////////////////////////// + + public ActionBar getSupportActionBar() { + return getSherlock().getActionBar(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + return getSherlock().startActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + + + /////////////////////////////////////////////////////////////////////////// + // General lifecycle/callback dispatching + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getSherlock().dispatchConfigurationChanged(newConfig); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getSherlock().dispatchPostResume(); + } + + @Override + protected void onPause() { + getSherlock().dispatchPause(); + super.onPause(); + } + + @Override + protected void onStop() { + getSherlock().dispatchStop(); + super.onStop(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + getSherlock().dispatchPostCreate(savedInstanceState); + super.onPostCreate(savedInstanceState); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + getSherlock().dispatchTitleChanged(title, color); + super.onTitleChanged(title, color); + } + + @Override + public final boolean onMenuOpened(int featureId, android.view.Menu menu) { + if (getSherlock().dispatchMenuOpened(featureId, menu)) { + return true; + } + return super.onMenuOpened(featureId, menu); + } + + @Override + public void onPanelClosed(int featureId, android.view.Menu menu) { + getSherlock().dispatchPanelClosed(featureId, menu); + super.onPanelClosed(featureId, menu); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (getSherlock().dispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + + /////////////////////////////////////////////////////////////////////////// + // Native menu handling + /////////////////////////////////////////////////////////////////////////// + + public MenuInflater getSupportMenuInflater() { + return getSherlock().getMenuInflater(); + } + + public void invalidateOptionsMenu() { + getSherlock().dispatchInvalidateOptionsMenu(); + } + + public void supportInvalidateOptionsMenu() { + invalidateOptionsMenu(); + } + + @Override + public final boolean onCreateOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchCreateOptionsMenu(menu); + } + + @Override + public final boolean onPrepareOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchPrepareOptionsMenu(menu); + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + return getSherlock().dispatchOptionsItemSelected(item); + } + + @Override + public void openOptionsMenu() { + if (!getSherlock().dispatchOpenOptionsMenu()) { + super.openOptionsMenu(); + } + } + + @Override + public void closeOptionsMenu() { + if (!getSherlock().dispatchCloseOptionsMenu()) { + super.closeOptionsMenu(); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // Sherlock menu handling + /////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onCreateOptionsMenu(menu); + } + return false; + } + + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onPrepareOptionsMenu(menu); + } + return false; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onOptionsItemSelected(item); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + + /////////////////////////////////////////////////////////////////////////// + // Content + /////////////////////////////////////////////////////////////////////////// + + @Override + public void addContentView(View view, LayoutParams params) { + getSherlock().addContentView(view, params); + } + + @Override + public void setContentView(int layoutResId) { + getSherlock().setContentView(layoutResId); + } + + @Override + public void setContentView(View view, LayoutParams params) { + getSherlock().setContentView(view, params); + } + + @Override + public void setContentView(View view) { + getSherlock().setContentView(view); + } + + public void requestWindowFeature(long featureId) { + getSherlock().requestFeature((int)featureId); + } + + + /////////////////////////////////////////////////////////////////////////// + // Progress Indication + /////////////////////////////////////////////////////////////////////////// + + public void setSupportProgress(int progress) { + getSherlock().setProgress(progress); + } + + public void setSupportProgressBarIndeterminate(boolean indeterminate) { + getSherlock().setProgressBarIndeterminate(indeterminate); + } + + public void setSupportProgressBarIndeterminateVisibility(boolean visible) { + getSherlock().setProgressBarIndeterminateVisibility(visible); + } + + public void setSupportProgressBarVisibility(boolean visible) { + getSherlock().setProgressBarVisibility(visible); + } + + public void setSupportSecondaryProgress(int secondaryProgress) { + getSherlock().setSecondaryProgress(secondaryProgress); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragment.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragment.java new file mode 100644 index 000000000..f02f5e433 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragment.java @@ -0,0 +1,72 @@ +package com.actionbarsherlock.app; + +import static com.actionbarsherlock.app.SherlockFragmentActivity.DEBUG; +import android.app.Activity; +import android.support.v4.app.Fragment; +import android.util.Log; +import com.actionbarsherlock.internal.view.menu.MenuItemMule; +import com.actionbarsherlock.internal.view.menu.MenuMule; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public class SherlockFragment extends Fragment { + private static final String TAG = "SherlockFragment"; + + private SherlockFragmentActivity mActivity; + + public SherlockFragmentActivity getSherlockActivity() { + return mActivity; + } + + @Override + public void onAttach(Activity activity) { + if (!(activity instanceof SherlockFragmentActivity)) { + throw new IllegalStateException(TAG + " must be attached to a SherlockFragmentActivity."); + } + mActivity = (SherlockFragmentActivity)activity; + + super.onAttach(activity); + } + + @Override + public final void onCreateOptionsMenu(android.view.Menu menu, android.view.MenuInflater inflater) { + if (DEBUG) Log.d(TAG, "[onCreateOptionsMenu] menu: " + menu + ", inflater: " + inflater); + + if (menu instanceof MenuMule) { + onCreateOptionsMenu(((MenuMule)menu).unwrap(), mActivity.getSupportMenuInflater()); + } + } + + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + //Nothing to see here. + } + + @Override + public final void onPrepareOptionsMenu(android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[onPrepareOptionsMenu] menu: " + menu); + + if (menu instanceof MenuMule) { + onPrepareOptionsMenu(((MenuMule)menu).unwrap()); + } + } + + public void onPrepareOptionsMenu(Menu menu) { + //Nothing to see here. + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + if (DEBUG) Log.d(TAG, "[onOptionsItemSelected] item: " + item); + + if (item instanceof MenuItemMule) { + return onOptionsItemSelected(((MenuItemMule)item).unwrap()); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + //Nothing to see here. + return false; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java new file mode 100644 index 000000000..479235713 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java @@ -0,0 +1,347 @@ +package com.actionbarsherlock.app; + +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.Window; +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeFinishedListener; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeStartedListener; +import com.actionbarsherlock.ActionBarSherlock.OnCreatePanelMenuListener; +import com.actionbarsherlock.ActionBarSherlock.OnMenuItemSelectedListener; +import com.actionbarsherlock.ActionBarSherlock.OnPreparePanelListener; +import com.actionbarsherlock.internal.view.menu.MenuItemMule; +import com.actionbarsherlock.internal.view.menu.MenuMule; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public abstract class SherlockFragmentActivity extends FragmentActivity implements OnCreatePanelMenuListener, OnPreparePanelListener, OnMenuItemSelectedListener, OnActionModeStartedListener, OnActionModeFinishedListener { + static final boolean DEBUG = false; + private static final String TAG = "SherlockFragmentActivity"; + + private ActionBarSherlock mSherlock; + private boolean mIgnoreNativeCreate = false; + private boolean mIgnoreNativePrepare = false; + private boolean mIgnoreNativeSelected = false; + private Boolean mOverrideNativeCreate = null; + + protected final ActionBarSherlock getSherlock() { + if (mSherlock == null) { + mSherlock = ActionBarSherlock.wrap(this, ActionBarSherlock.FLAG_DELEGATE); + } + return mSherlock; + } + + + /////////////////////////////////////////////////////////////////////////// + // Action bar and mode + /////////////////////////////////////////////////////////////////////////// + + public ActionBar getSupportActionBar() { + return getSherlock().getActionBar(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + return getSherlock().startActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + + + /////////////////////////////////////////////////////////////////////////// + // General lifecycle/callback dispatching + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getSherlock().dispatchConfigurationChanged(newConfig); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getSherlock().dispatchPostResume(); + } + + @Override + protected void onPause() { + getSherlock().dispatchPause(); + super.onPause(); + } + + @Override + protected void onStop() { + getSherlock().dispatchStop(); + super.onStop(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + getSherlock().dispatchPostCreate(savedInstanceState); + super.onPostCreate(savedInstanceState); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + getSherlock().dispatchTitleChanged(title, color); + super.onTitleChanged(title, color); + } + + @Override + public final boolean onMenuOpened(int featureId, android.view.Menu menu) { + if (getSherlock().dispatchMenuOpened(featureId, menu)) { + return true; + } + return super.onMenuOpened(featureId, menu); + } + + @Override + public void onPanelClosed(int featureId, android.view.Menu menu) { + getSherlock().dispatchPanelClosed(featureId, menu); + super.onPanelClosed(featureId, menu); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (getSherlock().dispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + + /////////////////////////////////////////////////////////////////////////// + // Native menu handling + /////////////////////////////////////////////////////////////////////////// + + public MenuInflater getSupportMenuInflater() { + if (DEBUG) Log.d(TAG, "[getSupportMenuInflater]"); + + return getSherlock().getMenuInflater(); + } + + public void invalidateOptionsMenu() { + if (DEBUG) Log.d(TAG, "[invalidateOptionsMenu]"); + + getSherlock().dispatchInvalidateOptionsMenu(); + } + + protected void supportInvalidateOptionsMenu() { + if (DEBUG) Log.d(TAG, "[supportInvalidateOptionsMenu]"); + + invalidateOptionsMenu(); + } + + @Override + public final boolean onCreatePanelMenu(int featureId, android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[onCreatePanelMenu] featureId: " + featureId + ", menu: " + menu); + + if (featureId == Window.FEATURE_OPTIONS_PANEL && !mIgnoreNativeCreate) { + mIgnoreNativeCreate = true; + boolean result = getSherlock().dispatchCreateOptionsMenu(menu); + mIgnoreNativeCreate = false; + + if (DEBUG) Log.d(TAG, "[onCreatePanelMenu] returning " + result); + return result; + } + return super.onCreatePanelMenu(featureId, menu); + } + + @Override + public final boolean onCreateOptionsMenu(android.view.Menu menu) { + return (mOverrideNativeCreate != null) ? mOverrideNativeCreate.booleanValue() : true; + } + + @Override + public final boolean onPreparePanel(int featureId, View view, android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[onPreparePanel] featureId: " + featureId + ", view: " + view + ", menu: " + menu); + + if (featureId == Window.FEATURE_OPTIONS_PANEL && !mIgnoreNativePrepare) { + mIgnoreNativePrepare = true; + boolean result = getSherlock().dispatchPrepareOptionsMenu(menu); + mIgnoreNativePrepare = false; + + if (DEBUG) Log.d(TAG, "[onPreparePanel] returning " + result); + return result; + } + return super.onPreparePanel(featureId, view, menu); + } + + @Override + public final boolean onPrepareOptionsMenu(android.view.Menu menu) { + return true; + } + + @Override + public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) { + if (DEBUG) Log.d(TAG, "[onMenuItemSelected] featureId: " + featureId + ", item: " + item); + + if (featureId == Window.FEATURE_OPTIONS_PANEL && !mIgnoreNativeSelected) { + mIgnoreNativeSelected = true; + boolean result = getSherlock().dispatchOptionsItemSelected(item); + mIgnoreNativeSelected = false; + + if (DEBUG) Log.d(TAG, "[onMenuItemSelected] returning " + result); + return result; + } + return super.onMenuItemSelected(featureId, item); + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + return false; + } + + @Override + public void openOptionsMenu() { + if (!getSherlock().dispatchOpenOptionsMenu()) { + super.openOptionsMenu(); + } + } + + @Override + public void closeOptionsMenu() { + if (!getSherlock().dispatchCloseOptionsMenu()) { + super.closeOptionsMenu(); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // Sherlock menu handling + /////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (DEBUG) Log.d(TAG, "[onCreatePanelMenu] featureId: " + featureId + ", menu: " + menu); + + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + boolean result = onCreateOptionsMenu(menu); + + //Dispatch to parent panel creation for fragment dispatching + if (DEBUG) Log.d(TAG, "[onCreatePanelMenu] dispatching to native with mule"); + mOverrideNativeCreate = result; + boolean fragResult = super.onCreatePanelMenu(featureId, new MenuMule(menu)); + mOverrideNativeCreate = null; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + result |= menu.hasVisibleItems(); + } else { + result |= fragResult; + } + + return result; + } + return false; + } + + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (DEBUG) Log.d(TAG, "[onPreparePanel] featureId: " + featureId + ", view: " + view + " menu: " + menu); + + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + boolean result = onPrepareOptionsMenu(menu); + + //Dispatch to parent panel preparation for fragment dispatching + if (DEBUG) Log.d(TAG, "[onPreparePanel] dispatching to native with mule"); + super.onPreparePanel(featureId, view, new MenuMule(menu)); + + return result; + } + return false; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (DEBUG) Log.d(TAG, "[onMenuItemSelected] featureId: " + featureId + ", item: " + item); + + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + boolean result = onOptionsItemSelected(item); + + //Dispatch to parent panel selection for fragment dispatching + if (DEBUG) Log.d(TAG, "[onMenuItemSelected] dispatching to native with mule"); + result |= super.onMenuItemSelected(featureId, new MenuItemMule(item)); + + return result; + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + + /////////////////////////////////////////////////////////////////////////// + // Content + /////////////////////////////////////////////////////////////////////////// + + @Override + public void addContentView(View view, LayoutParams params) { + getSherlock().addContentView(view, params); + } + + @Override + public void setContentView(int layoutResId) { + getSherlock().setContentView(layoutResId); + } + + @Override + public void setContentView(View view, LayoutParams params) { + getSherlock().setContentView(view, params); + } + + @Override + public void setContentView(View view) { + getSherlock().setContentView(view); + } + + public void requestWindowFeature(long featureId) { + getSherlock().requestFeature((int)featureId); + } + + + /////////////////////////////////////////////////////////////////////////// + // Progress Indication + /////////////////////////////////////////////////////////////////////////// + + public void setSupportProgress(int progress) { + getSherlock().setProgress(progress); + } + + public void setSupportProgressBarIndeterminate(boolean indeterminate) { + getSherlock().setProgressBarIndeterminate(indeterminate); + } + + public void setSupportProgressBarIndeterminateVisibility(boolean visible) { + getSherlock().setProgressBarIndeterminateVisibility(visible); + } + + public void setSupportProgressBarVisibility(boolean visible) { + getSherlock().setProgressBarVisibility(visible); + } + + public void setSupportSecondaryProgress(int secondaryProgress) { + getSherlock().setSecondaryProgress(secondaryProgress); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListActivity.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListActivity.java new file mode 100644 index 000000000..83114c3aa --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListActivity.java @@ -0,0 +1,253 @@ +package com.actionbarsherlock.app; + +import android.app.ListActivity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.view.ViewGroup.LayoutParams; +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeFinishedListener; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeStartedListener; +import com.actionbarsherlock.ActionBarSherlock.OnCreatePanelMenuListener; +import com.actionbarsherlock.ActionBarSherlock.OnMenuItemSelectedListener; +import com.actionbarsherlock.ActionBarSherlock.OnPreparePanelListener; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public abstract class SherlockListActivity extends ListActivity implements OnCreatePanelMenuListener, OnPreparePanelListener, OnMenuItemSelectedListener, OnActionModeStartedListener, OnActionModeFinishedListener { + private ActionBarSherlock mSherlock; + + protected final ActionBarSherlock getSherlock() { + if (mSherlock == null) { + mSherlock = ActionBarSherlock.wrap(this, ActionBarSherlock.FLAG_DELEGATE); + } + return mSherlock; + } + + + /////////////////////////////////////////////////////////////////////////// + // Action bar and mode + /////////////////////////////////////////////////////////////////////////// + + public ActionBar getSupportActionBar() { + return getSherlock().getActionBar(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + return getSherlock().startActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + + + /////////////////////////////////////////////////////////////////////////// + // General lifecycle/callback dispatching + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getSherlock().dispatchConfigurationChanged(newConfig); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getSherlock().dispatchPostResume(); + } + + @Override + protected void onPause() { + getSherlock().dispatchPause(); + super.onPause(); + } + + @Override + protected void onStop() { + getSherlock().dispatchStop(); + super.onStop(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + getSherlock().dispatchPostCreate(savedInstanceState); + super.onPostCreate(savedInstanceState); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + getSherlock().dispatchTitleChanged(title, color); + super.onTitleChanged(title, color); + } + + @Override + public final boolean onMenuOpened(int featureId, android.view.Menu menu) { + if (getSherlock().dispatchMenuOpened(featureId, menu)) { + return true; + } + return super.onMenuOpened(featureId, menu); + } + + @Override + public void onPanelClosed(int featureId, android.view.Menu menu) { + getSherlock().dispatchPanelClosed(featureId, menu); + super.onPanelClosed(featureId, menu); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (getSherlock().dispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + + /////////////////////////////////////////////////////////////////////////// + // Native menu handling + /////////////////////////////////////////////////////////////////////////// + + public MenuInflater getSupportMenuInflater() { + return getSherlock().getMenuInflater(); + } + + public void invalidateOptionsMenu() { + getSherlock().dispatchInvalidateOptionsMenu(); + } + + public void supportInvalidateOptionsMenu() { + invalidateOptionsMenu(); + } + + @Override + public final boolean onCreateOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchCreateOptionsMenu(menu); + } + + @Override + public final boolean onPrepareOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchPrepareOptionsMenu(menu); + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + return getSherlock().dispatchOptionsItemSelected(item); + } + + @Override + public void openOptionsMenu() { + if (!getSherlock().dispatchOpenOptionsMenu()) { + super.openOptionsMenu(); + } + } + + @Override + public void closeOptionsMenu() { + if (!getSherlock().dispatchCloseOptionsMenu()) { + super.closeOptionsMenu(); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // Sherlock menu handling + /////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onCreateOptionsMenu(menu); + } + return false; + } + + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onPrepareOptionsMenu(menu); + } + return false; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onOptionsItemSelected(item); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + + /////////////////////////////////////////////////////////////////////////// + // Content + /////////////////////////////////////////////////////////////////////////// + + @Override + public void addContentView(View view, LayoutParams params) { + getSherlock().addContentView(view, params); + } + + @Override + public void setContentView(int layoutResId) { + getSherlock().setContentView(layoutResId); + } + + @Override + public void setContentView(View view, LayoutParams params) { + getSherlock().setContentView(view, params); + } + + @Override + public void setContentView(View view) { + getSherlock().setContentView(view); + } + + public void requestWindowFeature(long featureId) { + getSherlock().requestFeature((int)featureId); + } + + + /////////////////////////////////////////////////////////////////////////// + // Progress Indication + /////////////////////////////////////////////////////////////////////////// + + public void setSupportProgress(int progress) { + getSherlock().setProgress(progress); + } + + public void setSupportProgressBarIndeterminate(boolean indeterminate) { + getSherlock().setProgressBarIndeterminate(indeterminate); + } + + public void setSupportProgressBarIndeterminateVisibility(boolean visible) { + getSherlock().setProgressBarIndeterminateVisibility(visible); + } + + public void setSupportProgressBarVisibility(boolean visible) { + getSherlock().setProgressBarVisibility(visible); + } + + public void setSupportSecondaryProgress(int secondaryProgress) { + getSherlock().setSecondaryProgress(secondaryProgress); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListFragment.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListFragment.java new file mode 100644 index 000000000..4f6891e1b --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockListFragment.java @@ -0,0 +1,72 @@ +package com.actionbarsherlock.app; + +import static com.actionbarsherlock.app.SherlockFragmentActivity.DEBUG; +import android.app.Activity; +import android.support.v4.app.ListFragment; +import android.util.Log; +import com.actionbarsherlock.internal.view.menu.MenuItemMule; +import com.actionbarsherlock.internal.view.menu.MenuMule; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public class SherlockListFragment extends ListFragment { + private static final String TAG = "SherlockListFragment"; + + private SherlockFragmentActivity mActivity; + + public SherlockFragmentActivity getSherlockActivity() { + return mActivity; + } + + @Override + public void onAttach(Activity activity) { + if (!(activity instanceof SherlockFragmentActivity)) { + throw new IllegalStateException(TAG + " must be attached to a SherlockFragmentActivity."); + } + mActivity = (SherlockFragmentActivity)activity; + + super.onAttach(activity); + } + + @Override + public final void onCreateOptionsMenu(android.view.Menu menu, android.view.MenuInflater inflater) { + if (DEBUG) Log.d(TAG, "[onCreateOptionsMenu] menu: " + menu + ", inflater: " + inflater); + + if (menu instanceof MenuMule) { + onCreateOptionsMenu(((MenuMule)menu).unwrap(), mActivity.getSupportMenuInflater()); + } + } + + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + //Nothing to see here. + } + + @Override + public final void onPrepareOptionsMenu(android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[onPrepareOptionsMenu] menu: " + menu); + + if (menu instanceof MenuMule) { + onPrepareOptionsMenu(((MenuMule)menu).unwrap()); + } + } + + public void onPrepareOptionsMenu(Menu menu) { + //Nothing to see here. + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + if (DEBUG) Log.d(TAG, "[onOptionsItemSelected] item: " + item); + + if (item instanceof MenuItemMule) { + return onOptionsItemSelected(((MenuItemMule)item).unwrap()); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + //Nothing to see here. + return false; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java new file mode 100644 index 000000000..71e40300a --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java @@ -0,0 +1,253 @@ +package com.actionbarsherlock.app; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.Window; +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeFinishedListener; +import com.actionbarsherlock.ActionBarSherlock.OnActionModeStartedListener; +import com.actionbarsherlock.ActionBarSherlock.OnCreatePanelMenuListener; +import com.actionbarsherlock.ActionBarSherlock.OnMenuItemSelectedListener; +import com.actionbarsherlock.ActionBarSherlock.OnPreparePanelListener; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public abstract class SherlockPreferenceActivity extends PreferenceActivity implements OnCreatePanelMenuListener, OnPreparePanelListener, OnMenuItemSelectedListener, OnActionModeStartedListener, OnActionModeFinishedListener { + private ActionBarSherlock mSherlock; + + protected final ActionBarSherlock getSherlock() { + if (mSherlock == null) { + mSherlock = ActionBarSherlock.wrap(this, ActionBarSherlock.FLAG_DELEGATE); + } + return mSherlock; + } + + + /////////////////////////////////////////////////////////////////////////// + // Action bar and mode + /////////////////////////////////////////////////////////////////////////// + + public ActionBar getSupportActionBar() { + return getSherlock().getActionBar(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + return getSherlock().startActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + + + /////////////////////////////////////////////////////////////////////////// + // General lifecycle/callback dispatching + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getSherlock().dispatchConfigurationChanged(newConfig); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getSherlock().dispatchPostResume(); + } + + @Override + protected void onPause() { + getSherlock().dispatchPause(); + super.onPause(); + } + + @Override + protected void onStop() { + getSherlock().dispatchStop(); + super.onStop(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + getSherlock().dispatchPostCreate(savedInstanceState); + super.onPostCreate(savedInstanceState); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + getSherlock().dispatchTitleChanged(title, color); + super.onTitleChanged(title, color); + } + + @Override + public final boolean onMenuOpened(int featureId, android.view.Menu menu) { + if (getSherlock().dispatchMenuOpened(featureId, menu)) { + return true; + } + return super.onMenuOpened(featureId, menu); + } + + @Override + public void onPanelClosed(int featureId, android.view.Menu menu) { + getSherlock().dispatchPanelClosed(featureId, menu); + super.onPanelClosed(featureId, menu); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (getSherlock().dispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + + /////////////////////////////////////////////////////////////////////////// + // Native menu handling + /////////////////////////////////////////////////////////////////////////// + + public MenuInflater getSupportMenuInflater() { + return getSherlock().getMenuInflater(); + } + + public void invalidateOptionsMenu() { + getSherlock().dispatchInvalidateOptionsMenu(); + } + + public void supportInvalidateOptionsMenu() { + invalidateOptionsMenu(); + } + + @Override + public final boolean onCreateOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchCreateOptionsMenu(menu); + } + + @Override + public final boolean onPrepareOptionsMenu(android.view.Menu menu) { + return getSherlock().dispatchPrepareOptionsMenu(menu); + } + + @Override + public final boolean onOptionsItemSelected(android.view.MenuItem item) { + return getSherlock().dispatchOptionsItemSelected(item); + } + + @Override + public void openOptionsMenu() { + if (!getSherlock().dispatchOpenOptionsMenu()) { + super.openOptionsMenu(); + } + } + + @Override + public void closeOptionsMenu() { + if (!getSherlock().dispatchCloseOptionsMenu()) { + super.closeOptionsMenu(); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // Sherlock menu handling + /////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onCreateOptionsMenu(menu); + } + return false; + } + + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onPrepareOptionsMenu(menu); + } + return false; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onOptionsItemSelected(item); + } + return false; + } + + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + + /////////////////////////////////////////////////////////////////////////// + // Content + /////////////////////////////////////////////////////////////////////////// + + @Override + public void addContentView(View view, LayoutParams params) { + getSherlock().addContentView(view, params); + } + + @Override + public void setContentView(int layoutResId) { + getSherlock().setContentView(layoutResId); + } + + @Override + public void setContentView(View view, LayoutParams params) { + getSherlock().setContentView(view, params); + } + + @Override + public void setContentView(View view) { + getSherlock().setContentView(view); + } + + public void requestWindowFeature(long featureId) { + getSherlock().requestFeature((int)featureId); + } + + + /////////////////////////////////////////////////////////////////////////// + // Progress Indication + /////////////////////////////////////////////////////////////////////////// + + public void setSupportProgress(int progress) { + getSherlock().setProgress(progress); + } + + public void setSupportProgressBarIndeterminate(boolean indeterminate) { + getSherlock().setProgressBarIndeterminate(indeterminate); + } + + public void setSupportProgressBarIndeterminateVisibility(boolean visible) { + getSherlock().setProgressBarIndeterminateVisibility(visible); + } + + public void setSupportProgressBarVisibility(boolean visible) { + getSherlock().setProgressBarVisibility(visible); + } + + public void setSupportSecondaryProgress(int secondaryProgress) { + getSherlock().setSecondaryProgress(secondaryProgress); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java new file mode 100644 index 000000000..eab4d2153 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java @@ -0,0 +1,1187 @@ +package com.actionbarsherlock.internal; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import org.xmlpull.v1.XmlPullParser; +import android.app.Activity; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.util.AndroidRuntimeException; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStub; +import android.view.Window; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.TextView; +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.R; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.internal.app.ActionBarImpl; +import com.actionbarsherlock.internal.view.StandaloneActionMode; +import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter; +import com.actionbarsherlock.internal.view.menu.MenuBuilder; +import com.actionbarsherlock.internal.view.menu.MenuItemImpl; +import com.actionbarsherlock.internal.view.menu.MenuPresenter; +import com.actionbarsherlock.internal.widget.ActionBarContainer; +import com.actionbarsherlock.internal.widget.ActionBarContextView; +import com.actionbarsherlock.internal.widget.ActionBarView; +import com.actionbarsherlock.internal.widget.IcsProgressBar; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +@ActionBarSherlock.Implementation(api = 7) +public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBuilder.Callback, com.actionbarsherlock.view.Window.Callback, MenuPresenter.Callback, android.view.MenuItem.OnMenuItemClickListener { + /** Window features which are enabled by default. */ + protected static final int DEFAULT_FEATURES = 0; + + + public ActionBarSherlockCompat(Activity activity, int flags) { + super(activity, flags); + } + + + /////////////////////////////////////////////////////////////////////////// + // Properties + /////////////////////////////////////////////////////////////////////////// + + /** Whether or not the device has a dedicated menu key button. */ + private boolean mReserveOverflow; + /** Lazy-load indicator for {@link #mReserveOverflow}. */ + private boolean mReserveOverflowSet = false; + + /** Current menu instance for managing action items. */ + private MenuBuilder mMenu; + /** Map between native options items and sherlock items. */ + protected HashMap mNativeItemMap; + + /** Parent view of the window decoration (action bar, mode, etc.). */ + private ViewGroup mDecor; + /** Parent view of the activity content. */ + private ViewGroup mContentParent; + + /** Whether or not the title is stable and can be displayed. */ + private boolean mIsTitleReady = false; + + /* Emulate PanelFeatureState */ + private boolean mClosingActionMenu; + private boolean mMenuIsPrepared; + private boolean mMenuRefreshContent; + private Bundle mMenuFrozenActionViewState; + + /** Implementation which backs the action bar interface API. */ + private ActionBarImpl aActionBar; + /** Main action bar view which displays the core content. */ + private ActionBarView wActionBar; + /** Relevant window and action bar features flags. */ + private int mFeatures = DEFAULT_FEATURES; + /** Relevant user interface option flags. */ + private int mUiOptions = 0; + + /** Decor indeterminate progress indicator. */ + private IcsProgressBar mCircularProgressBar; + /** Decor progress indicator. */ + private IcsProgressBar mHorizontalProgressBar; + + /** Current displayed context action bar, if any. */ + private ActionMode mActionMode; + /** Parent view in which the context action bar is displayed. */ + private ActionBarContextView mActionModeView; + + /** Title view used with dialogs. */ + private TextView mTitleView; + /** Current activity title. */ + private CharSequence mTitle = null; + /** Whether or not this "activity" is floating (i.e., a dialog) */ + private boolean mIsFloating; + + + + /////////////////////////////////////////////////////////////////////////// + // Instance methods + /////////////////////////////////////////////////////////////////////////// + + @Override + public ActionBar getActionBar() { + if (DEBUG) Log.d(TAG, "[getActionBar]"); + + initActionBar(); + return aActionBar; + } + + private void initActionBar() { + if (DEBUG) Log.d(TAG, "[initActionBar]"); + + // Initializing the window decor can change window feature flags. + // Make sure that we have the correct set before performing the test below. + if (mDecor == null) { + installDecor(); + } + + if ((aActionBar != null) || !hasFeature(Window.FEATURE_ACTION_BAR) || hasFeature(Window.FEATURE_NO_TITLE) || mActivity.isChild()) { + return; + } + + aActionBar = new ActionBarImpl(mActivity, mFeatures); + + if (!mIsDelegate) { + //We may never get another chance to set the title + wActionBar.setWindowTitle(mActivity.getTitle()); + } + } + + @Override + protected Context getThemedContext() { + return aActionBar.getThemedContext(); + } + + @Override + public void setTitle(CharSequence title) { + if (DEBUG) Log.d(TAG, "[setTitle] title: " + title); + + dispatchTitleChanged(title, 0); + } + + @Override + public ActionMode startActionMode(ActionMode.Callback callback) { + if (DEBUG) Log.d(TAG, "[startActionMode] callback: " + callback); + + if (mActionMode != null) { + mActionMode.finish(); + } + + final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); + ActionMode mode = null; + + //Emulate Activity's onWindowStartingActionMode: + initActionBar(); + if (aActionBar != null) { + mode = aActionBar.startActionMode(wrappedCallback); + } + + if (mode != null) { + mActionMode = mode; + } else { + if (mActionModeView == null) { + ViewStub stub = (ViewStub)mDecor.findViewById(R.id.abs__action_mode_bar_stub); + if (stub != null) { + mActionModeView = (ActionBarContextView)stub.inflate(); + } + } + if (mActionModeView != null) { + mActionModeView.killMode(); + mode = new StandaloneActionMode(mActivity, mActionModeView, wrappedCallback, true); + if (callback.onCreateActionMode(mode, mode.getMenu())) { + mode.invalidate(); + mActionModeView.initForMode(mode); + mActionModeView.setVisibility(View.VISIBLE); + mActionMode = mode; + mActionModeView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } else { + mActionMode = null; + } + } + } + if (mActionMode != null && mActivity instanceof OnActionModeStartedListener) { + ((OnActionModeStartedListener)mActivity).onActionModeStarted(mActionMode); + } + return mActionMode; + } + + + /////////////////////////////////////////////////////////////////////////// + // Lifecycle and interaction callbacks for delegation + /////////////////////////////////////////////////////////////////////////// + + @Override + public void dispatchConfigurationChanged(Configuration newConfig) { + if (DEBUG) Log.d(TAG, "[dispatchConfigurationChanged] newConfig: " + newConfig); + + if (aActionBar != null) { + aActionBar.onConfigurationChanged(newConfig); + } + } + + @Override + public void dispatchPostResume() { + if (DEBUG) Log.d(TAG, "[dispatchPostResume]"); + + if (aActionBar != null) { + aActionBar.setShowHideAnimationEnabled(true); + } + } + + @Override + public void dispatchPause() { + if (DEBUG) Log.d(TAG, "[dispatchPause]"); + + if (wActionBar != null && wActionBar.isOverflowMenuShowing()) { + wActionBar.hideOverflowMenu(); + } + } + + @Override + public void dispatchStop() { + if (DEBUG) Log.d(TAG, "[dispatchStop]"); + + if (aActionBar != null) { + aActionBar.setShowHideAnimationEnabled(false); + } + } + + @Override + public void dispatchInvalidateOptionsMenu() { + if (DEBUG) Log.d(TAG, "[dispatchInvalidateOptionsMenu]"); + + Bundle savedActionViewStates = null; + if (mMenu != null) { + savedActionViewStates = new Bundle(); + mMenu.saveActionViewStates(savedActionViewStates); + if (savedActionViewStates.size() > 0) { + mMenuFrozenActionViewState = savedActionViewStates; + } + // This will be started again when the panel is prepared. + mMenu.stopDispatchingItemsChanged(); + mMenu.clear(); + } + mMenuRefreshContent = true; + + // Prepare the options panel if we have an action bar + if (wActionBar != null) { + mMenuIsPrepared = false; + preparePanel(); + } + } + + @Override + public boolean dispatchOpenOptionsMenu() { + if (DEBUG) Log.d(TAG, "[dispatchOpenOptionsMenu]"); + + if (!isReservingOverflow()) { + return false; + } + + return wActionBar.showOverflowMenu(); + } + + @Override + public boolean dispatchCloseOptionsMenu() { + if (DEBUG) Log.d(TAG, "[dispatchCloseOptionsMenu]"); + + if (!isReservingOverflow()) { + return false; + } + + return wActionBar.hideOverflowMenu(); + } + + @Override + public void dispatchPostCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "[dispatchOnPostCreate]"); + + if (mIsDelegate) { + mIsTitleReady = true; + } + + if (mDecor == null) { + initActionBar(); + } + } + + @Override + public boolean dispatchCreateOptionsMenu(android.view.Menu menu) { + if (DEBUG) { + Log.d(TAG, "[dispatchCreateOptionsMenu] android.view.Menu: " + menu); + Log.d(TAG, "[dispatchCreateOptionsMenu] returning true"); + } + return true; + } + + @Override + public boolean dispatchPrepareOptionsMenu(android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu] android.view.Menu: " + menu); + + mMenuIsPrepared = false; + if (!preparePanel()) { + return false; + } + + if (isReservingOverflow()) { + return false; + } + + if (mNativeItemMap == null) { + mNativeItemMap = new HashMap(); + } else { + mNativeItemMap.clear(); + } + + if (mMenu == null) { + return false; + } + + boolean result = mMenu.bindNativeOverflow(menu, this, mNativeItemMap); + if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu] returning " + result); + return result; + } + + @Override + public boolean dispatchOptionsItemSelected(android.view.MenuItem item) { + throw new IllegalStateException("Native callback invoked. Create a test case and report!"); + } + + @Override + public boolean dispatchMenuOpened(int featureId, android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[dispatchMenuOpened] featureId: " + featureId + ", menu: " + menu); + + if (featureId == Window.FEATURE_ACTION_BAR || featureId == Window.FEATURE_OPTIONS_PANEL) { + if (aActionBar != null) { + aActionBar.dispatchMenuVisibilityChanged(true); + } + return true; + } + + return false; + } + + @Override + public void dispatchPanelClosed(int featureId, android.view.Menu menu){ + if (DEBUG) Log.d(TAG, "[dispatchPanelClosed] featureId: " + featureId + ", menu: " + menu); + + if (featureId == Window.FEATURE_ACTION_BAR || featureId == Window.FEATURE_OPTIONS_PANEL) { + if (aActionBar != null) { + aActionBar.dispatchMenuVisibilityChanged(false); + } + } + } + + @Override + public void dispatchTitleChanged(CharSequence title, int color) { + if (DEBUG) Log.d(TAG, "[dispatchTitleChanged] title: " + title + ", color: " + color); + + if (!mIsDelegate || mIsTitleReady) { + if (mTitleView != null) { + mTitleView.setText(title); + } else if (wActionBar != null) { + wActionBar.setWindowTitle(title); + } + } + + mTitle = title; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] event: " + event); + + final int keyCode = event.getKeyCode(); + + // Not handled by the view hierarchy, does the action bar want it + // to cancel out of something special? + if (keyCode == KeyEvent.KEYCODE_BACK) { + final int action = event.getAction(); + // Back cancels action modes first. + if (mActionMode != null) { + if (action == KeyEvent.ACTION_UP) { + mActionMode.finish(); + } + if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning true"); + return true; + } + + // Next collapse any expanded action views. + if (aActionBar != null && wActionBar.hasExpandedActionView()) { + if (action == KeyEvent.ACTION_UP) { + wActionBar.collapseActionView(); + } + if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning true"); + return true; + } + } + + if (keyCode == KeyEvent.KEYCODE_MENU && event.getAction() == KeyEvent.ACTION_UP && isReservingOverflow()) { + if (mActionMode == null) { + if (wActionBar.isOverflowMenuShowing()) { + wActionBar.hideOverflowMenu(); + } else { + wActionBar.showOverflowMenu(); + } + } + if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning true"); + return true; + } + + if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning false"); + return false; + } + + + /////////////////////////////////////////////////////////////////////////// + // Menu callback lifecycle and creation + /////////////////////////////////////////////////////////////////////////// + + private boolean preparePanel() { + // Already prepared (isPrepared will be reset to false later) + if (mMenuIsPrepared) { + return true; + } + + // Init the panel state's menu--return false if init failed + if (mMenu == null || mMenuRefreshContent) { + if (mMenu == null) { + if (!initializePanelMenu() || (mMenu == null)) { + return false; + } + } + + if (wActionBar != null) { + wActionBar.setMenu(mMenu, this); + } + + // Call callback, and return if it doesn't want to display menu. + + // Creating the panel menu will involve a lot of manipulation; + // don't dispatch change events to presenters until we're done. + mMenu.stopDispatchingItemsChanged(); + if (!callbackCreateOptionsMenu(mMenu)) { + // Ditch the menu created above + mMenu = null; + + if (wActionBar != null) { + // Don't show it in the action bar either + wActionBar.setMenu(null, this); + } + + return false; + } + + mMenuRefreshContent = false; + } + + // Callback and return if the callback does not want to show the menu + + // Preparing the panel menu can involve a lot of manipulation; + // don't dispatch change events to presenters until we're done. + mMenu.stopDispatchingItemsChanged(); + + // Restore action view state before we prepare. This gives apps + // an opportunity to override frozen/restored state in onPrepare. + if (mMenuFrozenActionViewState != null) { + mMenu.restoreActionViewStates(mMenuFrozenActionViewState); + mMenuFrozenActionViewState = null; + } + + if (!callbackPrepareOptionsMenu(mMenu)) { + if (wActionBar != null) { + // The app didn't want to show the menu for now but it still exists. + // Clear it out of the action bar. + wActionBar.setMenu(null, this); + } + mMenu.startDispatchingItemsChanged(); + return false; + } + + // Set the proper keymap + KeyCharacterMap kmap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + mMenu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC); + mMenu.startDispatchingItemsChanged(); + + // Set other state + mMenuIsPrepared = true; + + return true; + } + + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + return callbackOptionsItemSelected(item); + } + + public void onMenuModeChange(MenuBuilder menu) { + reopenMenu(true); + } + + private void reopenMenu(boolean toggleMenuMode) { + if (wActionBar != null && wActionBar.isOverflowReserved()) { + if (!wActionBar.isOverflowMenuShowing() || !toggleMenuMode) { + if (wActionBar.getVisibility() == View.VISIBLE) { + if (callbackPrepareOptionsMenu(mMenu)) { + wActionBar.showOverflowMenu(); + } + } + } else { + wActionBar.hideOverflowMenu(); + } + return; + } + } + + private boolean initializePanelMenu() { + Context context = mActivity;//getContext(); + + // If we have an action bar, initialize the menu with a context themed for it. + if (wActionBar != null) { + TypedValue outValue = new TypedValue(); + Resources.Theme currentTheme = context.getTheme(); + currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme, + outValue, true); + final int targetThemeRes = outValue.resourceId; + + if (targetThemeRes != 0 /*&& context.getThemeResId() != targetThemeRes*/) { + context = new ContextThemeWrapper(context, targetThemeRes); + } + } + + mMenu = new MenuBuilder(context); + mMenu.setCallback(this); + + return true; + } + + void checkCloseActionMenu(Menu menu) { + if (mClosingActionMenu) { + return; + } + + mClosingActionMenu = true; + wActionBar.dismissPopupMenus(); + //Callback cb = getCallback(); + //if (cb != null && !isDestroyed()) { + // cb.onPanelClosed(FEATURE_ACTION_BAR, menu); + //} + mClosingActionMenu = false; + } + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + return true; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + checkCloseActionMenu(menu); + } + + @Override + public boolean onMenuItemClick(android.view.MenuItem item) { + if (DEBUG) Log.d(TAG, "[mNativeItemListener.onMenuItemClick] item: " + item); + + final MenuItemImpl sherlockItem = mNativeItemMap.get(item); + if (sherlockItem != null) { + sherlockItem.invoke(); + } else { + Log.e(TAG, "Options item \"" + item + "\" not found in mapping"); + } + + return true; //Do not allow continuation of native handling + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return callbackOptionsItemSelected(item); + } + + + /////////////////////////////////////////////////////////////////////////// + // Progress bar interaction and internal handling + /////////////////////////////////////////////////////////////////////////// + + @Override + public void setProgressBarVisibility(boolean visible) { + if (DEBUG) Log.d(TAG, "[setProgressBarVisibility] visible: " + visible); + + setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON : + Window.PROGRESS_VISIBILITY_OFF); + } + + @Override + public void setProgressBarIndeterminateVisibility(boolean visible) { + if (DEBUG) Log.d(TAG, "[setProgressBarIndeterminateVisibility] visible: " + visible); + + setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS, + visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF); + } + + @Override + public void setProgressBarIndeterminate(boolean indeterminate) { + if (DEBUG) Log.d(TAG, "[setProgressBarIndeterminate] indeterminate: " + indeterminate); + + setFeatureInt(Window.FEATURE_PROGRESS, + indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF); + } + + @Override + public void setProgress(int progress) { + if (DEBUG) Log.d(TAG, "[setProgress] progress: " + progress); + + setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START); + } + + @Override + public void setSecondaryProgress(int secondaryProgress) { + if (DEBUG) Log.d(TAG, "[setSecondaryProgress] secondaryProgress: " + secondaryProgress); + + setFeatureInt(Window.FEATURE_PROGRESS, + secondaryProgress + Window.PROGRESS_SECONDARY_START); + } + + private void setFeatureInt(int featureId, int value) { + updateInt(featureId, value, false); + } + + private void updateInt(int featureId, int value, boolean fromResume) { + // Do nothing if the decor is not yet installed... an update will + // need to be forced when we eventually become active. + if (mContentParent == null) { + return; + } + + final int featureMask = 1 << featureId; + + if ((getFeatures() & featureMask) == 0 && !fromResume) { + return; + } + + onIntChanged(featureId, value); + } + + private void onIntChanged(int featureId, int value) { + if (featureId == Window.FEATURE_PROGRESS || featureId == Window.FEATURE_INDETERMINATE_PROGRESS) { + updateProgressBars(value); + } + } + + private void updateProgressBars(int value) { + IcsProgressBar circularProgressBar = getCircularProgressBar(true); + IcsProgressBar horizontalProgressBar = getHorizontalProgressBar(true); + + final int features = mFeatures;//getLocalFeatures(); + if (value == Window.PROGRESS_VISIBILITY_ON) { + if ((features & (1 << Window.FEATURE_PROGRESS)) != 0) { + int level = horizontalProgressBar.getProgress(); + int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? + View.VISIBLE : View.INVISIBLE; + horizontalProgressBar.setVisibility(visibility); + } + if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0) { + circularProgressBar.setVisibility(View.VISIBLE); + } + } else if (value == Window.PROGRESS_VISIBILITY_OFF) { + if ((features & (1 << Window.FEATURE_PROGRESS)) != 0) { + horizontalProgressBar.setVisibility(View.GONE); + } + if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0) { + circularProgressBar.setVisibility(View.GONE); + } + } else if (value == Window.PROGRESS_INDETERMINATE_ON) { + horizontalProgressBar.setIndeterminate(true); + } else if (value == Window.PROGRESS_INDETERMINATE_OFF) { + horizontalProgressBar.setIndeterminate(false); + } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) { + // We want to set the progress value before testing for visibility + // so that when the progress bar becomes visible again, it has the + // correct level. + horizontalProgressBar.setProgress(value - Window.PROGRESS_START); + + if (value < Window.PROGRESS_END) { + showProgressBars(horizontalProgressBar, circularProgressBar); + } else { + hideProgressBars(horizontalProgressBar, circularProgressBar); + } + } else if (Window.PROGRESS_SECONDARY_START <= value && value <= Window.PROGRESS_SECONDARY_END) { + horizontalProgressBar.setSecondaryProgress(value - Window.PROGRESS_SECONDARY_START); + + showProgressBars(horizontalProgressBar, circularProgressBar); + } + } + + private void showProgressBars(IcsProgressBar horizontalProgressBar, IcsProgressBar spinnyProgressBar) { + final int features = mFeatures;//getLocalFeatures(); + if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0 && + spinnyProgressBar.getVisibility() == View.INVISIBLE) { + spinnyProgressBar.setVisibility(View.VISIBLE); + } + // Only show the progress bars if the primary progress is not complete + if ((features & (1 << Window.FEATURE_PROGRESS)) != 0 && + horizontalProgressBar.getProgress() < 10000) { + horizontalProgressBar.setVisibility(View.VISIBLE); + } + } + + private void hideProgressBars(IcsProgressBar horizontalProgressBar, IcsProgressBar spinnyProgressBar) { + final int features = mFeatures;//getLocalFeatures(); + Animation anim = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out); + anim.setDuration(1000); + if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0 && + spinnyProgressBar.getVisibility() == View.VISIBLE) { + spinnyProgressBar.startAnimation(anim); + spinnyProgressBar.setVisibility(View.INVISIBLE); + } + if ((features & (1 << Window.FEATURE_PROGRESS)) != 0 && + horizontalProgressBar.getVisibility() == View.VISIBLE) { + horizontalProgressBar.startAnimation(anim); + horizontalProgressBar.setVisibility(View.INVISIBLE); + } + } + + private IcsProgressBar getCircularProgressBar(boolean shouldInstallDecor) { + if (mCircularProgressBar != null) { + return mCircularProgressBar; + } + if (mContentParent == null && shouldInstallDecor) { + installDecor(); + } + mCircularProgressBar = (IcsProgressBar)mDecor.findViewById(R.id.abs__progress_circular); + if (mCircularProgressBar != null) { + mCircularProgressBar.setVisibility(View.INVISIBLE); + } + return mCircularProgressBar; + } + + private IcsProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) { + if (mHorizontalProgressBar != null) { + return mHorizontalProgressBar; + } + if (mContentParent == null && shouldInstallDecor) { + installDecor(); + } + mHorizontalProgressBar = (IcsProgressBar)mDecor.findViewById(R.id.abs__progress_horizontal); + if (mHorizontalProgressBar != null) { + mHorizontalProgressBar.setVisibility(View.INVISIBLE); + } + return mHorizontalProgressBar; + } + + + /////////////////////////////////////////////////////////////////////////// + // Feature management and content interaction and creation + /////////////////////////////////////////////////////////////////////////// + + private int getFeatures() { + if (DEBUG) Log.d(TAG, "[getFeatures] returning " + mFeatures); + + return mFeatures; + } + + @Override + public boolean hasFeature(int featureId) { + if (DEBUG) Log.d(TAG, "[hasFeature] featureId: " + featureId); + + boolean result = (mFeatures & (1 << featureId)) != 0; + if (DEBUG) Log.d(TAG, "[hasFeature] returning " + result); + return result; + } + + @Override + public boolean requestFeature(int featureId) { + if (DEBUG) Log.d(TAG, "[requestFeature] featureId: " + featureId); + + if (mContentParent != null) { + throw new AndroidRuntimeException("requestFeature() must be called before adding content"); + } + + switch (featureId) { + case Window.FEATURE_ACTION_BAR: + case Window.FEATURE_ACTION_BAR_OVERLAY: + case Window.FEATURE_ACTION_MODE_OVERLAY: + case Window.FEATURE_INDETERMINATE_PROGRESS: + case Window.FEATURE_NO_TITLE: + case Window.FEATURE_PROGRESS: + mFeatures |= (1 << featureId); + return true; + + default: + return false; + } + } + + @Override + public void setUiOptions(int uiOptions) { + if (DEBUG) Log.d(TAG, "[setUiOptions] uiOptions: " + uiOptions); + + mUiOptions = uiOptions; + } + + @Override + public void setUiOptions(int uiOptions, int mask) { + if (DEBUG) Log.d(TAG, "[setUiOptions] uiOptions: " + uiOptions + ", mask: " + mask); + + mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask); + } + + @Override + public void setContentView(int layoutResId) { + if (DEBUG) Log.d(TAG, "[setContentView] layoutResId: " + layoutResId); + + if (mContentParent == null) { + installDecor(); + } else { + mContentParent.removeAllViews(); + } + mActivity.getLayoutInflater().inflate(layoutResId, mContentParent); + + android.view.Window.Callback callback = mActivity.getWindow().getCallback(); + if (callback != null) { + callback.onContentChanged(); + } + + initActionBar(); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + if (DEBUG) Log.d(TAG, "[setContentView] view: " + view + ", params: " + params); + + if (mContentParent == null) { + installDecor(); + } else { + mContentParent.removeAllViews(); + } + mContentParent.addView(view, params); + + android.view.Window.Callback callback = mActivity.getWindow().getCallback(); + if (callback != null) { + callback.onContentChanged(); + } + + initActionBar(); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + if (DEBUG) Log.d(TAG, "[addContentView] view: " + view + ", params: " + params); + + if (mContentParent == null) { + installDecor(); + } + mContentParent.addView(view, params); + + initActionBar(); + } + + private void installDecor() { + if (DEBUG) Log.d(TAG, "[installDecor]"); + + if (mDecor == null) { + mDecor = (ViewGroup)mActivity.getWindow().getDecorView().findViewById(android.R.id.content); + } + if (mContentParent == null) { + //Since we are not operating at the window level we need to take + //into account the fact that the true decor may have already been + //initialized and had content attached to it. If that is the case, + //copy over its children to our new content container. + List views = null; + if (mDecor.getChildCount() > 0) { + views = new ArrayList(1); //Usually there's only one child + for (int i = 0, children = mDecor.getChildCount(); i < children; i++) { + View child = mDecor.getChildAt(0); + mDecor.removeView(child); + views.add(child); + } + } + + mContentParent = generateLayout(); + + //Copy over the old children. See above for explanation. + if (views != null) { + for (View child : views) { + mContentParent.addView(child); + } + } + + mTitleView = (TextView)mDecor.findViewById(android.R.id.title); + if (mTitleView != null) { + if (hasFeature(Window.FEATURE_NO_TITLE)) { + mTitleView.setVisibility(View.GONE); + if (mContentParent instanceof FrameLayout) { + ((FrameLayout)mContentParent).setForeground(null); + } + } else { + mTitleView.setText(mTitle); + } + } else { + wActionBar = (ActionBarView)mDecor.findViewById(R.id.abs__action_bar); + if (wActionBar != null) { + wActionBar.setWindowCallback(this); + if (wActionBar.getTitle() == null) { + wActionBar.setWindowTitle(mActivity.getTitle()); + } + if (hasFeature(Window.FEATURE_PROGRESS)) { + wActionBar.initProgress(); + } + if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) { + wActionBar.initIndeterminateProgress(); + } + + //Since we don't require onCreate dispatching, parse for uiOptions here + int uiOptions = loadUiOptionsFromManifest(mActivity); + if (uiOptions != 0) { + mUiOptions = uiOptions; + } + + boolean splitActionBar = false; + final boolean splitWhenNarrow = (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0; + if (splitWhenNarrow) { + splitActionBar = getResources_getBoolean(mActivity, R.bool.abs__split_action_bar_is_narrow); + } else { + splitActionBar = mActivity.getTheme() + .obtainStyledAttributes(R.styleable.SherlockTheme) + .getBoolean(R.styleable.SherlockTheme_windowSplitActionBar, false); + } + final ActionBarContainer splitView = (ActionBarContainer)mDecor.findViewById(R.id.abs__split_action_bar); + if (splitView != null) { + wActionBar.setSplitView(splitView); + wActionBar.setSplitActionBar(splitActionBar); + wActionBar.setSplitWhenNarrow(splitWhenNarrow); + + mActionModeView = (ActionBarContextView)mDecor.findViewById(R.id.abs__action_context_bar); + mActionModeView.setSplitView(splitView); + mActionModeView.setSplitActionBar(splitActionBar); + mActionModeView.setSplitWhenNarrow(splitWhenNarrow); + } else if (splitActionBar) { + Log.e(TAG, "Requested split action bar with incompatible window decor! Ignoring request."); + } + + // Post the panel invalidate for later; avoid application onCreateOptionsMenu + // being called in the middle of onCreate or similar. + mDecor.post(new Runnable() { + @Override + public void run() { + //Invalidate if the panel menu hasn't been created before this. + if (mMenu == null) { + dispatchInvalidateOptionsMenu(); + } + } + }); + } + } + } + } + + private ViewGroup generateLayout() { + if (DEBUG) Log.d(TAG, "[generateLayout]"); + + // Apply data from current theme. + + TypedArray a = mActivity.getTheme().obtainStyledAttributes(R.styleable.SherlockTheme); + + mIsFloating = a.getBoolean(R.styleable.SherlockTheme_android_windowIsFloating, false); + + if (!a.hasValue(R.styleable.SherlockTheme_windowActionBar)) { + throw new IllegalStateException("You must use Theme.Sherlock, Theme.Sherlock.Light, Theme.Sherlock.Light.DarkActionBar, or a derivative."); + } + + if (a.getBoolean(R.styleable.SherlockTheme_windowNoTitle, false)) { + requestFeature(Window.FEATURE_NO_TITLE); + } else if (a.getBoolean(R.styleable.SherlockTheme_windowActionBar, false)) { + // Don't allow an action bar if there is no title. + requestFeature(Window.FEATURE_ACTION_BAR); + } + + if (a.getBoolean(R.styleable.SherlockTheme_windowActionBarOverlay, false)) { + requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + } + + if (a.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false)) { + requestFeature(Window.FEATURE_ACTION_MODE_OVERLAY); + } + + a.recycle(); + + int layoutResource; + if (!hasFeature(Window.FEATURE_NO_TITLE)) { + if (mIsFloating) { + //Trash original dialog LinearLayout + mDecor = (ViewGroup)mDecor.getParent(); + mDecor.removeAllViews(); + + layoutResource = R.layout.abs__dialog_title_holo; + } else { + if (hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) { + layoutResource = R.layout.abs__screen_action_bar_overlay; + } else { + layoutResource = R.layout.abs__screen_action_bar; + } + } + } else if (hasFeature(Window.FEATURE_ACTION_MODE_OVERLAY) && !hasFeature(Window.FEATURE_NO_TITLE)) { + layoutResource = R.layout.abs__screen_simple_overlay_action_mode; + } else { + layoutResource = R.layout.abs__screen_simple; + } + + if (DEBUG) Log.d(TAG, "[generateLayout] using screen XML " + mActivity.getResources().getString(layoutResource)); + View in = mActivity.getLayoutInflater().inflate(layoutResource, null); + mDecor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + + ViewGroup contentParent = (ViewGroup)mDecor.findViewById(R.id.abs__content); + if (contentParent == null) { + throw new RuntimeException("Couldn't find content container view"); + } + + //Make our new child the true content view (for fragments). VERY VOLATILE! + mDecor.setId(View.NO_ID); + contentParent.setId(android.R.id.content); + + if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) { + IcsProgressBar progress = getCircularProgressBar(false); + if (progress != null) { + progress.setIndeterminate(true); + } + } + + return contentParent; + } + + + /////////////////////////////////////////////////////////////////////////// + // Miscellaneous + /////////////////////////////////////////////////////////////////////////// + + /** + * Determine whether or not the device has a dedicated menu key. + * + * @return {@code true} if native menu key is present. + */ + private boolean isReservingOverflow() { + if (!mReserveOverflowSet) { + mReserveOverflow = ActionMenuPresenter.reserveOverflow(mActivity); + mReserveOverflowSet = true; + } + return mReserveOverflow; + } + + private static int loadUiOptionsFromManifest(Activity activity) { + int uiOptions = 0; + try { + final String thisPackage = activity.getClass().getName(); + if (DEBUG) Log.i(TAG, "Parsing AndroidManifest.xml for " + thisPackage); + + final String packageName = activity.getApplicationInfo().packageName; + final AssetManager am = activity.createPackageContext(packageName, 0).getAssets(); + final XmlResourceParser xml = am.openXmlResourceParser("AndroidManifest.xml"); + + int eventType = xml.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String name = xml.getName(); + + if ("application".equals(name)) { + //Check if the has the attribute + if (DEBUG) Log.d(TAG, "Got "); + + for (int i = xml.getAttributeCount() - 1; i >= 0; i--) { + if (DEBUG) Log.d(TAG, xml.getAttributeName(i) + ": " + xml.getAttributeValue(i)); + + if ("uiOptions".equals(xml.getAttributeName(i))) { + uiOptions = xml.getAttributeIntValue(i, 0); + break; //out of for loop + } + } + } else if ("activity".equals(name)) { + //Check if the is us and has the attribute + if (DEBUG) Log.d(TAG, "Got "); + Integer activityUiOptions = null; + String activityPackage = null; + boolean isOurActivity = false; + + for (int i = xml.getAttributeCount() - 1; i >= 0; i--) { + if (DEBUG) Log.d(TAG, xml.getAttributeName(i) + ": " + xml.getAttributeValue(i)); + + //We need both uiOptions and name attributes + String attrName = xml.getAttributeName(i); + if ("uiOptions".equals(attrName)) { + activityUiOptions = xml.getAttributeIntValue(i, 0); + } else if ("name".equals(attrName)) { + activityPackage = cleanActivityName(packageName, xml.getAttributeValue(i)); + if (!thisPackage.equals(activityPackage)) { + break; //out of for loop + } + isOurActivity = true; + } + + //Make sure we have both attributes before processing + if ((activityUiOptions != null) && (activityPackage != null)) { + //Our activity, uiOptions specified, override with our value + uiOptions = activityUiOptions.intValue(); + } + } + if (isOurActivity) { + //If we matched our activity but it had no logo don't + //do any more processing of the manifest + break; + } + } + } + eventType = xml.nextToken(); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (DEBUG) Log.i(TAG, "Returning " + Integer.toHexString(uiOptions)); + return uiOptions; + } + + public static String cleanActivityName(String manifestPackage, String activityName) { + if (activityName.charAt(0) == '.') { + //Relative activity name (e.g., android:name=".ui.SomeClass") + return manifestPackage + activityName; + } + if (activityName.indexOf('.', 1) == -1) { + //Unqualified activity name (e.g., android:name="SomeClass") + return manifestPackage + "." + activityName; + } + //Fully-qualified activity name (e.g., "com.my.package.SomeClass") + return activityName; + } + + /** + * Clears out internal reference when the action mode is destroyed. + */ + private class ActionModeCallbackWrapper implements ActionMode.Callback { + private final ActionMode.Callback mWrapped; + + public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { + mWrapped = wrapped; + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return mWrapped.onCreateActionMode(mode, menu); + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return mWrapped.onPrepareActionMode(mode, menu); + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return mWrapped.onActionItemClicked(mode, item); + } + + public void onDestroyActionMode(ActionMode mode) { + mWrapped.onDestroyActionMode(mode); + if (mActionModeView != null) { + mActionModeView.setVisibility(View.GONE); + mActionModeView.removeAllViews(); + } + if (mActivity instanceof OnActionModeFinishedListener) { + ((OnActionModeFinishedListener)mActivity).onActionModeFinished(mActionMode); + } + mActionMode = null; + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java new file mode 100644 index 000000000..74831a79e --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java @@ -0,0 +1,318 @@ +package com.actionbarsherlock.internal; + +import com.actionbarsherlock.ActionBarSherlock; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.internal.app.ActionBarWrapper; +import com.actionbarsherlock.internal.view.menu.MenuWrapper; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.MenuInflater; +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.Window; +import android.view.ViewGroup.LayoutParams; + +@ActionBarSherlock.Implementation(api = 14) +public class ActionBarSherlockNative extends ActionBarSherlock { + private ActionBarWrapper mActionBar; + private ActionModeWrapper mActionMode; + private MenuWrapper mMenu; + + public ActionBarSherlockNative(Activity activity, int flags) { + super(activity, flags); + } + + + @Override + public ActionBar getActionBar() { + if (DEBUG) Log.d(TAG, "[getActionBar]"); + + initActionBar(); + return mActionBar; + } + + private void initActionBar() { + if (mActionBar != null || mActivity.getActionBar() == null) { + return; + } + + mActionBar = new ActionBarWrapper(mActivity); + } + + @Override + public void dispatchInvalidateOptionsMenu() { + if (DEBUG) Log.d(TAG, "[dispatchInvalidateOptionsMenu]"); + + mActivity.getWindow().invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + } + + @Override + public boolean dispatchCreateOptionsMenu(android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[dispatchCreateOptionsMenu] menu: " + menu); + + if (mMenu == null || menu != mMenu.unwrap()) { + mMenu = new MenuWrapper(menu); + } + + final boolean result = callbackCreateOptionsMenu(mMenu); + if (DEBUG) Log.d(TAG, "[dispatchCreateOptionsMenu] returning " + result); + return result; + } + + @Override + public boolean dispatchPrepareOptionsMenu(android.view.Menu menu) { + if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu] menu: " + menu); + + final boolean result = callbackPrepareOptionsMenu(mMenu); + if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu] returning " + result); + return result; + } + + @Override + public boolean dispatchOptionsItemSelected(android.view.MenuItem item) { + if (DEBUG) Log.d(TAG, "[dispatchOptionsItemSelected] item: " + item.getTitleCondensed()); + + final boolean result = callbackOptionsItemSelected(mMenu.findItem(item)); + if (DEBUG) Log.d(TAG, "[dispatchOptionsItemSelected] returning " + result); + return result; + } + + @Override + public boolean hasFeature(int feature) { + if (DEBUG) Log.d(TAG, "[hasFeature] feature: " + feature); + + final boolean result = mActivity.getWindow().hasFeature(feature); + if (DEBUG) Log.d(TAG, "[hasFeature] returning " + result); + return result; + } + + @Override + public boolean requestFeature(int featureId) { + if (DEBUG) Log.d(TAG, "[requestFeature] featureId: " + featureId); + + final boolean result = mActivity.getWindow().requestFeature(featureId); + if (DEBUG) Log.d(TAG, "[requestFeature] returning " + result); + return result; + } + + @Override + public void setUiOptions(int uiOptions) { + if (DEBUG) Log.d(TAG, "[setUiOptions] uiOptions: " + uiOptions); + + mActivity.getWindow().setUiOptions(uiOptions); + } + + @Override + public void setUiOptions(int uiOptions, int mask) { + if (DEBUG) Log.d(TAG, "[setUiOptions] uiOptions: " + uiOptions + ", mask: " + mask); + + mActivity.getWindow().setUiOptions(uiOptions, mask); + } + + @Override + public void setContentView(int layoutResId) { + if (DEBUG) Log.d(TAG, "[setContentView] layoutResId: " + layoutResId); + + mActivity.getWindow().setContentView(layoutResId); + initActionBar(); + } + + @Override + public void setContentView(View view, LayoutParams params) { + if (DEBUG) Log.d(TAG, "[setContentView] view: " + view + ", params: " + params); + + mActivity.getWindow().setContentView(view, params); + initActionBar(); + } + + @Override + public void addContentView(View view, LayoutParams params) { + if (DEBUG) Log.d(TAG, "[addContentView] view: " + view + ", params: " + params); + + mActivity.getWindow().addContentView(view, params); + initActionBar(); + } + + @Override + public void setTitle(CharSequence title) { + if (DEBUG) Log.d(TAG, "[setTitle] title: " + title); + + mActivity.getWindow().setTitle(title); + } + + @Override + public void setProgressBarVisibility(boolean visible) { + if (DEBUG) Log.d(TAG, "[setProgressBarVisibility] visible: " + visible); + + mActivity.setProgressBarVisibility(visible); + } + + @Override + public void setProgressBarIndeterminateVisibility(boolean visible) { + if (DEBUG) Log.d(TAG, "[setProgressBarIndeterminateVisibility] visible: " + visible); + + mActivity.setProgressBarIndeterminateVisibility(visible); + } + + @Override + public void setProgressBarIndeterminate(boolean indeterminate) { + if (DEBUG) Log.d(TAG, "[setProgressBarIndeterminate] indeterminate: " + indeterminate); + + mActivity.setProgressBarIndeterminate(indeterminate); + } + + @Override + public void setProgress(int progress) { + if (DEBUG) Log.d(TAG, "[setProgress] progress: " + progress); + + mActivity.setProgress(progress); + } + + @Override + public void setSecondaryProgress(int secondaryProgress) { + if (DEBUG) Log.d(TAG, "[setSecondaryProgress] secondaryProgress: " + secondaryProgress); + + mActivity.setSecondaryProgress(secondaryProgress); + } + + @Override + protected Context getThemedContext() { + Context context = mActivity; + TypedValue outValue = new TypedValue(); + mActivity.getTheme().resolveAttribute(android.R.attr.actionBarWidgetTheme, outValue, true); + if (outValue.resourceId != 0) { + //We are unable to test if this is the same as our current theme + //so we just wrap it and hope that if the attribute was specified + //then the user is intentionally specifying an alternate theme. + context = new ContextThemeWrapper(context, outValue.resourceId); + } + return context; + } + + @Override + public ActionMode startActionMode(com.actionbarsherlock.view.ActionMode.Callback callback) { + if (DEBUG) Log.d(TAG, "[startActionMode] callback: " + callback); + + if (mActionMode != null) { + mActionMode.finish(); + } + ActionModeCallbackWrapper wrapped = null; + if (callback != null) { + wrapped = new ActionModeCallbackWrapper(callback); + } + + //Calling this will trigger the callback wrapper's onCreate which + //is where we will set the new instance to mActionMode since we need + //to pass it through to the sherlock callbacks and the call below + //will not have returned yet to store its value. + mActivity.startActionMode(wrapped); + + return mActionMode; + } + + private class ActionModeCallbackWrapper implements android.view.ActionMode.Callback { + private final ActionMode.Callback mCallback; + + public ActionModeCallbackWrapper(ActionMode.Callback callback) { + mCallback = callback; + } + + @Override + public boolean onCreateActionMode(android.view.ActionMode mode, android.view.Menu menu) { + //See ActionBarSherlockNative#startActionMode + mActionMode = new ActionModeWrapper(mode); + + return mCallback.onCreateActionMode(mActionMode, mActionMode.getMenu()); + } + + @Override + public boolean onPrepareActionMode(android.view.ActionMode mode, android.view.Menu menu) { + return mCallback.onPrepareActionMode(mActionMode, mActionMode.getMenu()); + } + + @Override + public boolean onActionItemClicked(android.view.ActionMode mode, android.view.MenuItem item) { + return mCallback.onActionItemClicked(mActionMode, mActionMode.getMenu().findItem(item)); + } + + @Override + public void onDestroyActionMode(android.view.ActionMode mode) { + mCallback.onDestroyActionMode(mActionMode); + } + } + + private class ActionModeWrapper extends ActionMode { + private final android.view.ActionMode mActionMode; + private MenuWrapper mMenu = null; + + ActionModeWrapper(android.view.ActionMode actionMode) { + mActionMode = actionMode; + } + + @Override + public void setTitle(CharSequence title) { + mActionMode.setTitle(title); + } + + @Override + public void setTitle(int resId) { + mActionMode.setTitle(resId); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mActionMode.setSubtitle(subtitle); + } + + @Override + public void setSubtitle(int resId) { + mActionMode.setSubtitle(resId); + } + + @Override + public void setCustomView(View view) { + mActionMode.setCustomView(view); + } + + @Override + public void invalidate() { + mActionMode.invalidate(); + } + + @Override + public void finish() { + mActionMode.finish(); + } + + @Override + public MenuWrapper getMenu() { + if (mMenu == null) { + mMenu = new MenuWrapper(mActionMode.getMenu()); + } + return mMenu; + } + + @Override + public CharSequence getTitle() { + return mActionMode.getTitle(); + } + + @Override + public CharSequence getSubtitle() { + return mActionMode.getSubtitle(); + } + + @Override + public View getCustomView() { + return mActionMode.getCustomView(); + } + + @Override + public MenuInflater getMenuInflater() { + return ActionBarSherlockNative.this.getMenuInflater(); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/ResourcesCompat.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/ResourcesCompat.java new file mode 100644 index 000000000..8e1efe8c5 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/ResourcesCompat.java @@ -0,0 +1,95 @@ +package com.actionbarsherlock.internal; + +import android.content.Context; +import android.os.Build; +import android.util.DisplayMetrics; +import com.actionbarsherlock.R; + +public final class ResourcesCompat { + //No instances + private ResourcesCompat() {} + + + /** + * Support implementation of {@code getResources().getBoolean()} that we + * can use to simulate filtering based on width and smallest width + * qualifiers on pre-3.2. + * + * @param context Context to load booleans from on 3.2+ and to fetch the + * display metrics. + * @param id Id of boolean to load. + * @return Associated boolean value as reflected by the current display + * metrics. + */ + public static boolean getResources_getBoolean(Context context, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + return context.getResources().getBoolean(id); + } + + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + float widthDp = metrics.widthPixels / metrics.density; + float heightDp = metrics.heightPixels / metrics.density; + float smallestWidthDp = (widthDp < heightDp) ? widthDp : heightDp; + + if (id == R.bool.abs__action_bar_embed_tabs) { + if (widthDp >= 480) { + return true; //values-w480dp + } + return false; //values + } + if (id == R.bool.abs__split_action_bar_is_narrow) { + if (widthDp >= 480) { + return false; //values-w480dp + } + return true; //values + } + if (id == R.bool.abs__action_bar_expanded_action_views_exclusive) { + if (smallestWidthDp >= 600) { + return false; //values-sw600dp + } + return true; //values + } + if (id == R.bool.abs__config_allowActionMenuItemTextWithIcon) { + if (widthDp >= 480) { + return true; //values-w480dp + } + return false; //values + } + + throw new IllegalArgumentException("Unknown boolean resource ID " + id); + } + + /** + * Support implementation of {@code getResources().getInteger()} that we + * can use to simulate filtering based on width qualifiers on pre-3.2. + * + * @param context Context to load integers from on 3.2+ and to fetch the + * display metrics. + * @param id Id of integer to load. + * @return Associated integer value as reflected by the current display + * metrics. + */ + public static int getResources_getInteger(Context context, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + return context.getResources().getInteger(id); + } + + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + float widthDp = metrics.widthPixels / metrics.density; + + if (id == R.integer.abs__max_action_buttons) { + if (widthDp >= 600) { + return 5; //values-w600dp + } + if (widthDp >= 500) { + return 4; //values-w500dp + } + if (widthDp >= 360) { + return 3; //values-w360dp + } + return 2; //values + } + + throw new IllegalArgumentException("Unknown integer resource ID " + id); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java new file mode 100644 index 000000000..6ae0402c0 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java @@ -0,0 +1,1026 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.app; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.support.v4.app.FragmentTransaction; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.accessibility.AccessibilityEvent; +import android.widget.SpinnerAdapter; +import com.actionbarsherlock.R; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator; +import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorListenerAdapter; +import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet; +import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator.AnimatorListener; +import com.actionbarsherlock.internal.nineoldandroids.widget.NineFrameLayout; +import com.actionbarsherlock.internal.view.menu.MenuBuilder; +import com.actionbarsherlock.internal.view.menu.MenuPopupHelper; +import com.actionbarsherlock.internal.view.menu.SubMenuBuilder; +import com.actionbarsherlock.internal.widget.ActionBarContainer; +import com.actionbarsherlock.internal.widget.ActionBarContextView; +import com.actionbarsherlock.internal.widget.ActionBarView; +import com.actionbarsherlock.internal.widget.ScrollingTabContainerView; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean; + +/** + * ActionBarImpl is the ActionBar implementation used + * by devices of all screen sizes. If it detects a compatible decor, + * it will split contextual modes across both the ActionBarView at + * the top of the screen and a horizontal LinearLayout at the bottom + * which is normally hidden. + */ +public class ActionBarImpl extends ActionBar { + //UNUSED private static final String TAG = "ActionBarImpl"; + + private Context mContext; + private Context mThemedContext; + private Activity mActivity; + //UNUSED private Dialog mDialog; + + private ActionBarContainer mContainerView; + private ActionBarView mActionView; + private ActionBarContextView mContextView; + private ActionBarContainer mSplitView; + private NineFrameLayout mContentView; + private ScrollingTabContainerView mTabScrollView; + + private ArrayList mTabs = new ArrayList(); + + private TabImpl mSelectedTab; + private int mSavedTabPosition = INVALID_POSITION; + + ActionModeImpl mActionMode; + ActionMode mDeferredDestroyActionMode; + ActionMode.Callback mDeferredModeDestroyCallback; + + private boolean mLastMenuVisibility; + private ArrayList mMenuVisibilityListeners = + new ArrayList(); + + private static final int CONTEXT_DISPLAY_NORMAL = 0; + private static final int CONTEXT_DISPLAY_SPLIT = 1; + + private static final int INVALID_POSITION = -1; + + private int mContextDisplayMode; + private boolean mHasEmbeddedTabs; + + final Handler mHandler = new Handler(); + Runnable mTabSelector; + + private Animator mCurrentShowAnim; + private Animator mCurrentModeAnim; + private boolean mShowHideAnimationEnabled; + boolean mWasHiddenBeforeMode; + + final AnimatorListener mHideListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mContentView != null) { + mContentView.setTranslationY(0); + mContainerView.setTranslationY(0); + } + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + mSplitView.setVisibility(View.GONE); + } + mContainerView.setVisibility(View.GONE); + mContainerView.setTransitioning(false); + mCurrentShowAnim = null; + completeDeferredDestroyActionMode(); + } + }; + + final AnimatorListener mShowListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentShowAnim = null; + mContainerView.requestLayout(); + } + }; + + public ActionBarImpl(Activity activity, int features) { + mActivity = activity; + Window window = activity.getWindow(); + View decor = window.getDecorView(); + init(decor); + + //window.hasFeature() workaround for pre-3.0 + if ((features & (1 << Window.FEATURE_ACTION_BAR_OVERLAY)) == 0) { + mContentView = (NineFrameLayout)decor.findViewById(android.R.id.content); + } + } + + public ActionBarImpl(Dialog dialog) { + //UNUSED mDialog = dialog; + init(dialog.getWindow().getDecorView()); + } + + private void init(View decor) { + mContext = decor.getContext(); + mActionView = (ActionBarView) decor.findViewById(R.id.abs__action_bar); + mContextView = (ActionBarContextView) decor.findViewById( + R.id.abs__action_context_bar); + mContainerView = (ActionBarContainer) decor.findViewById( + R.id.abs__action_bar_container); + mSplitView = (ActionBarContainer) decor.findViewById( + R.id.abs__split_action_bar); + + if (mActionView == null || mContextView == null || mContainerView == null) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + + "with a compatible window decor layout"); + } + + mActionView.setContextView(mContextView); + mContextDisplayMode = mActionView.isSplitActionBar() ? + CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL; + + // Older apps get the home button interaction enabled by default. + // Newer apps need to enable it explicitly. + setHomeButtonEnabled(mContext.getApplicationInfo().targetSdkVersion < 14); + + setHasEmbeddedTabs(getResources_getBoolean(mContext, + R.bool.abs__action_bar_embed_tabs)); + } + + public void onConfigurationChanged(Configuration newConfig) { + setHasEmbeddedTabs(getResources_getBoolean(mContext, + R.bool.abs__action_bar_embed_tabs)); + + //Manually dispatch a configuration change to the action bar view on pre-2.2 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { + mActionView.onConfigurationChanged(newConfig); + if (mContextView != null) { + mContextView.onConfigurationChanged(newConfig); + } + } + } + + private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) { + mHasEmbeddedTabs = hasEmbeddedTabs; + // Switch tab layout configuration if needed + if (!mHasEmbeddedTabs) { + mActionView.setEmbeddedTabView(null); + mContainerView.setTabContainer(mTabScrollView); + } else { + mContainerView.setTabContainer(null); + mActionView.setEmbeddedTabView(mTabScrollView); + } + final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; + if (mTabScrollView != null) { + mTabScrollView.setVisibility(isInTabMode ? View.VISIBLE : View.GONE); + } + mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode); + } + + private void ensureTabsExist() { + if (mTabScrollView != null) { + return; + } + + ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext); + + if (mHasEmbeddedTabs) { + tabScroller.setVisibility(View.VISIBLE); + mActionView.setEmbeddedTabView(tabScroller); + } else { + tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ? + View.VISIBLE : View.GONE); + mContainerView.setTabContainer(tabScroller); + } + mTabScrollView = tabScroller; + } + + void completeDeferredDestroyActionMode() { + if (mDeferredModeDestroyCallback != null) { + mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode); + mDeferredDestroyActionMode = null; + mDeferredModeDestroyCallback = null; + } + } + + /** + * Enables or disables animation between show/hide states. + * If animation is disabled using this method, animations in progress + * will be finished. + * + * @param enabled true to animate, false to not animate. + */ + public void setShowHideAnimationEnabled(boolean enabled) { + mShowHideAnimationEnabled = enabled; + if (!enabled && mCurrentShowAnim != null) { + mCurrentShowAnim.end(); + } + } + + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + mMenuVisibilityListeners.add(listener); + } + + public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + mMenuVisibilityListeners.remove(listener); + } + + public void dispatchMenuVisibilityChanged(boolean isVisible) { + if (isVisible == mLastMenuVisibility) { + return; + } + mLastMenuVisibility = isVisible; + + final int count = mMenuVisibilityListeners.size(); + for (int i = 0; i < count; i++) { + mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); + } + } + + @Override + public void setCustomView(int resId) { + setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false)); + } + + @Override + public void setDisplayUseLogoEnabled(boolean useLogo) { + setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO); + } + + @Override + public void setDisplayShowHomeEnabled(boolean showHome) { + setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME); + } + + @Override + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP); + } + + @Override + public void setDisplayShowTitleEnabled(boolean showTitle) { + setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE); + } + + @Override + public void setDisplayShowCustomEnabled(boolean showCustom) { + setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM); + } + + @Override + public void setHomeButtonEnabled(boolean enable) { + mActionView.setHomeButtonEnabled(enable); + } + + @Override + public void setTitle(int resId) { + setTitle(mContext.getString(resId)); + } + + @Override + public void setSubtitle(int resId) { + setSubtitle(mContext.getString(resId)); + } + + public void setSelectedNavigationItem(int position) { + switch (mActionView.getNavigationMode()) { + case NAVIGATION_MODE_TABS: + selectTab(mTabs.get(position)); + break; + case NAVIGATION_MODE_LIST: + mActionView.setDropdownSelectedPosition(position); + break; + default: + throw new IllegalStateException( + "setSelectedNavigationIndex not valid for current navigation mode"); + } + } + + public void removeAllTabs() { + cleanupTabs(); + } + + private void cleanupTabs() { + if (mSelectedTab != null) { + selectTab(null); + } + mTabs.clear(); + if (mTabScrollView != null) { + mTabScrollView.removeAllTabs(); + } + mSavedTabPosition = INVALID_POSITION; + } + + public void setTitle(CharSequence title) { + mActionView.setTitle(title); + } + + public void setSubtitle(CharSequence subtitle) { + mActionView.setSubtitle(subtitle); + } + + public void setDisplayOptions(int options) { + mActionView.setDisplayOptions(options); + } + + public void setDisplayOptions(int options, int mask) { + final int current = mActionView.getDisplayOptions(); + mActionView.setDisplayOptions((options & mask) | (current & ~mask)); + } + + public void setBackgroundDrawable(Drawable d) { + mContainerView.setPrimaryBackground(d); + } + + public void setStackedBackgroundDrawable(Drawable d) { + mContainerView.setStackedBackground(d); + } + + public void setSplitBackgroundDrawable(Drawable d) { + if (mSplitView != null) { + mSplitView.setSplitBackground(d); + } + } + + public View getCustomView() { + return mActionView.getCustomNavigationView(); + } + + public CharSequence getTitle() { + return mActionView.getTitle(); + } + + public CharSequence getSubtitle() { + return mActionView.getSubtitle(); + } + + public int getNavigationMode() { + return mActionView.getNavigationMode(); + } + + public int getDisplayOptions() { + return mActionView.getDisplayOptions(); + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + boolean wasHidden = false; + if (mActionMode != null) { + wasHidden = mWasHiddenBeforeMode; + mActionMode.finish(); + } + + mContextView.killMode(); + ActionModeImpl mode = new ActionModeImpl(callback); + if (mode.dispatchOnCreate()) { + mWasHiddenBeforeMode = !isShowing() || wasHidden; + mode.invalidate(); + mContextView.initForMode(mode); + animateToMode(true); + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + // TODO animate this + mSplitView.setVisibility(View.VISIBLE); + } + mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + mActionMode = mode; + return mode; + } + return null; + } + + private void configureTab(Tab tab, int position) { + final TabImpl tabi = (TabImpl) tab; + final ActionBar.TabListener callback = tabi.getCallback(); + + if (callback == null) { + throw new IllegalStateException("Action Bar Tab must have a Callback"); + } + + tabi.setPosition(position); + mTabs.add(position, tabi); + + final int count = mTabs.size(); + for (int i = position + 1; i < count; i++) { + mTabs.get(i).setPosition(i); + } + } + + @Override + public void addTab(Tab tab) { + addTab(tab, mTabs.isEmpty()); + } + + @Override + public void addTab(Tab tab, int position) { + addTab(tab, position, mTabs.isEmpty()); + } + + @Override + public void addTab(Tab tab, boolean setSelected) { + ensureTabsExist(); + mTabScrollView.addTab(tab, setSelected); + configureTab(tab, mTabs.size()); + if (setSelected) { + selectTab(tab); + } + } + + @Override + public void addTab(Tab tab, int position, boolean setSelected) { + ensureTabsExist(); + mTabScrollView.addTab(tab, position, setSelected); + configureTab(tab, position); + if (setSelected) { + selectTab(tab); + } + } + + @Override + public Tab newTab() { + return new TabImpl(); + } + + @Override + public void removeTab(Tab tab) { + removeTabAt(tab.getPosition()); + } + + @Override + public void removeTabAt(int position) { + if (mTabScrollView == null) { + // No tabs around to remove + return; + } + + int selectedTabPosition = mSelectedTab != null + ? mSelectedTab.getPosition() : mSavedTabPosition; + mTabScrollView.removeTabAt(position); + TabImpl removedTab = mTabs.remove(position); + if (removedTab != null) { + removedTab.setPosition(-1); + } + + final int newTabCount = mTabs.size(); + for (int i = position; i < newTabCount; i++) { + mTabs.get(i).setPosition(i); + } + + if (selectedTabPosition == position) { + selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); + } + } + + @Override + public void selectTab(Tab tab) { + if (getNavigationMode() != NAVIGATION_MODE_TABS) { + mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION; + return; + } + + FragmentTransaction trans = null; + if (mActivity instanceof SherlockFragmentActivity) { + trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction() + .disallowAddToBackStack(); + } + + if (mSelectedTab == tab) { + if (mSelectedTab != null) { + mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans); + mTabScrollView.animateToTab(tab.getPosition()); + } + } else { + mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION); + if (mSelectedTab != null) { + mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans); + } + mSelectedTab = (TabImpl) tab; + if (mSelectedTab != null) { + mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans); + } + } + + if (trans != null && !trans.isEmpty()) { + trans.commit(); + } + } + + @Override + public Tab getSelectedTab() { + return mSelectedTab; + } + + @Override + public int getHeight() { + return mContainerView.getHeight(); + } + + @Override + public void show() { + show(true); + } + + void show(boolean markHiddenBeforeMode) { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.end(); + } + if (mContainerView.getVisibility() == View.VISIBLE) { + if (markHiddenBeforeMode) mWasHiddenBeforeMode = false; + return; + } + mContainerView.setVisibility(View.VISIBLE); + + if (mShowHideAnimationEnabled) { + mContainerView.setAlpha(0); + AnimatorSet anim = new AnimatorSet(); + AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 1)); + if (mContentView != null) { + b.with(ObjectAnimator.ofFloat(mContentView, "translationY", + -mContainerView.getHeight(), 0)); + mContainerView.setTranslationY(-mContainerView.getHeight()); + b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0)); + } + if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { + mSplitView.setAlpha(0); + mSplitView.setVisibility(View.VISIBLE); + b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 1)); + } + anim.addListener(mShowListener); + mCurrentShowAnim = anim; + anim.start(); + } else { + mContainerView.setAlpha(1); + mContainerView.setTranslationY(0); + mShowListener.onAnimationEnd(null); + } + } + + @Override + public void hide() { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.end(); + } + if (mContainerView.getVisibility() == View.GONE) { + return; + } + + if (mShowHideAnimationEnabled) { + mContainerView.setAlpha(1); + mContainerView.setTransitioning(true); + AnimatorSet anim = new AnimatorSet(); + AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 0)); + if (mContentView != null) { + b.with(ObjectAnimator.ofFloat(mContentView, "translationY", + 0, -mContainerView.getHeight())); + b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", + -mContainerView.getHeight())); + } + if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) { + mSplitView.setAlpha(1); + b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 0)); + } + anim.addListener(mHideListener); + mCurrentShowAnim = anim; + anim.start(); + } else { + mHideListener.onAnimationEnd(null); + } + } + + public boolean isShowing() { + return mContainerView.getVisibility() == View.VISIBLE; + } + + void animateToMode(boolean toActionMode) { + if (toActionMode) { + show(false); + } + if (mCurrentModeAnim != null) { + mCurrentModeAnim.end(); + } + + mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); + if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) { + mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + } + } + + public Context getThemedContext() { + if (mThemedContext == null) { + TypedValue outValue = new TypedValue(); + Resources.Theme currentTheme = mContext.getTheme(); + currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme, + outValue, true); + final int targetThemeRes = outValue.resourceId; + + if (targetThemeRes != 0) { //XXX && mContext.getThemeResId() != targetThemeRes) { + mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes); + } else { + mThemedContext = mContext; + } + } + return mThemedContext; + } + + /** + * @hide + */ + public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback { + private ActionMode.Callback mCallback; + private MenuBuilder mMenu; + private WeakReference mCustomView; + + public ActionModeImpl(ActionMode.Callback callback) { + mCallback = callback; + mMenu = new MenuBuilder(getThemedContext()) + .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + mMenu.setCallback(this); + } + + @Override + public MenuInflater getMenuInflater() { + return new MenuInflater(getThemedContext()); + } + + @Override + public Menu getMenu() { + return mMenu; + } + + @Override + public void finish() { + if (mActionMode != this) { + // Not the active action mode - no-op + return; + } + + // If we were hidden before the mode was shown, defer the onDestroy + // callback until the animation is finished and associated relayout + // is about to happen. This lets apps better anticipate visibility + // and layout behavior. + if (mWasHiddenBeforeMode) { + mDeferredDestroyActionMode = this; + mDeferredModeDestroyCallback = mCallback; + } else { + mCallback.onDestroyActionMode(this); + } + mCallback = null; + animateToMode(false); + + // Clear out the context mode views after the animation finishes + mContextView.closeMode(); + mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + + mActionMode = null; + + if (mWasHiddenBeforeMode) { + hide(); + } + } + + @Override + public void invalidate() { + mMenu.stopDispatchingItemsChanged(); + try { + mCallback.onPrepareActionMode(this, mMenu); + } finally { + mMenu.startDispatchingItemsChanged(); + } + } + + public boolean dispatchOnCreate() { + mMenu.stopDispatchingItemsChanged(); + try { + return mCallback.onCreateActionMode(this, mMenu); + } finally { + mMenu.startDispatchingItemsChanged(); + } + } + + @Override + public void setCustomView(View view) { + mContextView.setCustomView(view); + mCustomView = new WeakReference(view); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mContextView.setSubtitle(subtitle); + } + + @Override + public void setTitle(CharSequence title) { + mContextView.setTitle(title); + } + + @Override + public void setTitle(int resId) { + setTitle(mContext.getResources().getString(resId)); + } + + @Override + public void setSubtitle(int resId) { + setSubtitle(mContext.getResources().getString(resId)); + } + + @Override + public CharSequence getTitle() { + return mContextView.getTitle(); + } + + @Override + public CharSequence getSubtitle() { + return mContextView.getSubtitle(); + } + + @Override + public View getCustomView() { + return mCustomView != null ? mCustomView.get() : null; + } + + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + if (mCallback != null) { + return mCallback.onActionItemClicked(this, item); + } else { + return false; + } + } + + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + } + + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + if (mCallback == null) { + return false; + } + + if (!subMenu.hasVisibleItems()) { + return true; + } + + new MenuPopupHelper(getThemedContext(), subMenu).show(); + return true; + } + + public void onCloseSubMenu(SubMenuBuilder menu) { + } + + public void onMenuModeChange(MenuBuilder menu) { + if (mCallback == null) { + return; + } + invalidate(); + mContextView.showOverflowMenu(); + } + } + + /** + * @hide + */ + public class TabImpl extends ActionBar.Tab { + private ActionBar.TabListener mCallback; + private Object mTag; + private Drawable mIcon; + private CharSequence mText; + private CharSequence mContentDesc; + private int mPosition = -1; + private View mCustomView; + + @Override + public Object getTag() { + return mTag; + } + + @Override + public Tab setTag(Object tag) { + mTag = tag; + return this; + } + + public ActionBar.TabListener getCallback() { + return mCallback; + } + + @Override + public Tab setTabListener(ActionBar.TabListener callback) { + mCallback = callback; + return this; + } + + @Override + public View getCustomView() { + return mCustomView; + } + + @Override + public Tab setCustomView(View view) { + mCustomView = view; + if (mPosition >= 0) { + mTabScrollView.updateTab(mPosition); + } + return this; + } + + @Override + public Tab setCustomView(int layoutResId) { + return setCustomView(LayoutInflater.from(getThemedContext()) + .inflate(layoutResId, null)); + } + + @Override + public Drawable getIcon() { + return mIcon; + } + + @Override + public int getPosition() { + return mPosition; + } + + public void setPosition(int position) { + mPosition = position; + } + + @Override + public CharSequence getText() { + return mText; + } + + @Override + public Tab setIcon(Drawable icon) { + mIcon = icon; + if (mPosition >= 0) { + mTabScrollView.updateTab(mPosition); + } + return this; + } + + @Override + public Tab setIcon(int resId) { + return setIcon(mContext.getResources().getDrawable(resId)); + } + + @Override + public Tab setText(CharSequence text) { + mText = text; + if (mPosition >= 0) { + mTabScrollView.updateTab(mPosition); + } + return this; + } + + @Override + public Tab setText(int resId) { + return setText(mContext.getResources().getText(resId)); + } + + @Override + public void select() { + selectTab(this); + } + + @Override + public Tab setContentDescription(int resId) { + return setContentDescription(mContext.getResources().getText(resId)); + } + + @Override + public Tab setContentDescription(CharSequence contentDesc) { + mContentDesc = contentDesc; + if (mPosition >= 0) { + mTabScrollView.updateTab(mPosition); + } + return this; + } + + @Override + public CharSequence getContentDescription() { + return mContentDesc; + } + } + + @Override + public void setCustomView(View view) { + mActionView.setCustomNavigationView(view); + } + + @Override + public void setCustomView(View view, LayoutParams layoutParams) { + view.setLayoutParams(layoutParams); + mActionView.setCustomNavigationView(view); + } + + @Override + public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { + mActionView.setDropdownAdapter(adapter); + mActionView.setCallback(callback); + } + + @Override + public int getSelectedNavigationIndex() { + switch (mActionView.getNavigationMode()) { + case NAVIGATION_MODE_TABS: + return mSelectedTab != null ? mSelectedTab.getPosition() : -1; + case NAVIGATION_MODE_LIST: + return mActionView.getDropdownSelectedPosition(); + default: + return -1; + } + } + + @Override + public int getNavigationItemCount() { + switch (mActionView.getNavigationMode()) { + case NAVIGATION_MODE_TABS: + return mTabs.size(); + case NAVIGATION_MODE_LIST: + SpinnerAdapter adapter = mActionView.getDropdownAdapter(); + return adapter != null ? adapter.getCount() : 0; + default: + return 0; + } + } + + @Override + public int getTabCount() { + return mTabs.size(); + } + + @Override + public void setNavigationMode(int mode) { + final int oldMode = mActionView.getNavigationMode(); + switch (oldMode) { + case NAVIGATION_MODE_TABS: + mSavedTabPosition = getSelectedNavigationIndex(); + selectTab(null); + mTabScrollView.setVisibility(View.GONE); + break; + } + mActionView.setNavigationMode(mode); + switch (mode) { + case NAVIGATION_MODE_TABS: + ensureTabsExist(); + mTabScrollView.setVisibility(View.VISIBLE); + if (mSavedTabPosition != INVALID_POSITION) { + setSelectedNavigationItem(mSavedTabPosition); + mSavedTabPosition = INVALID_POSITION; + } + break; + } + mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + } + + @Override + public Tab getTabAt(int index) { + return mTabs.get(index); + } + + + @Override + public void setIcon(int resId) { + mActionView.setIcon(resId); + } + + @Override + public void setIcon(Drawable icon) { + mActionView.setIcon(icon); + } + + @Override + public void setLogo(int resId) { + mActionView.setLogo(resId); + } + + @Override + public void setLogo(Drawable logo) { + mActionView.setLogo(logo); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java new file mode 100644 index 000000000..1fdb9278c --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java @@ -0,0 +1,462 @@ +package com.actionbarsherlock.internal.app; + +import java.util.HashSet; +import java.util.Set; + +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.widget.SpinnerAdapter; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockFragmentActivity; + +public class ActionBarWrapper extends ActionBar implements android.app.ActionBar.OnNavigationListener, android.app.ActionBar.OnMenuVisibilityListener { + private final Activity mActivity; + private final android.app.ActionBar mActionBar; + private ActionBar.OnNavigationListener mNavigationListener; + private Set mMenuVisibilityListeners = new HashSet(1); + private FragmentTransaction mFragmentTransaction; + + + public ActionBarWrapper(Activity activity) { + mActivity = activity; + mActionBar = activity.getActionBar(); + if (mActionBar != null) { + mActionBar.addOnMenuVisibilityListener(this); + } + } + + + @Override + public void setHomeButtonEnabled(boolean enabled) { + mActionBar.setHomeButtonEnabled(enabled); + } + + @Override + public Context getThemedContext() { + return mActionBar.getThemedContext(); + } + + @Override + public void setCustomView(View view) { + mActionBar.setCustomView(view); + } + + @Override + public void setCustomView(View view, LayoutParams layoutParams) { + android.app.ActionBar.LayoutParams lp = new android.app.ActionBar.LayoutParams(layoutParams); + lp.gravity = layoutParams.gravity; + lp.bottomMargin = layoutParams.bottomMargin; + lp.topMargin = layoutParams.topMargin; + lp.leftMargin = layoutParams.leftMargin; + lp.rightMargin = layoutParams.rightMargin; + mActionBar.setCustomView(view, lp); + } + + @Override + public void setCustomView(int resId) { + mActionBar.setCustomView(resId); + } + + @Override + public void setIcon(int resId) { + mActionBar.setIcon(resId); + } + + @Override + public void setIcon(Drawable icon) { + mActionBar.setIcon(icon); + } + + @Override + public void setLogo(int resId) { + mActionBar.setLogo(resId); + } + + @Override + public void setLogo(Drawable logo) { + mActionBar.setLogo(logo); + } + + @Override + public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { + mNavigationListener = callback; + mActionBar.setListNavigationCallbacks(adapter, (callback != null) ? this : null); + } + + @Override + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + //This should never be a NullPointerException since we only set + //ourselves as the listener when the callback is not null. + return mNavigationListener.onNavigationItemSelected(itemPosition, itemId); + } + + @Override + public void setSelectedNavigationItem(int position) { + mActionBar.setSelectedNavigationItem(position); + } + + @Override + public int getSelectedNavigationIndex() { + return mActionBar.getSelectedNavigationIndex(); + } + + @Override + public int getNavigationItemCount() { + return mActionBar.getNavigationItemCount(); + } + + @Override + public void setTitle(CharSequence title) { + mActionBar.setTitle(title); + } + + @Override + public void setTitle(int resId) { + mActionBar.setTitle(resId); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mActionBar.setSubtitle(subtitle); + } + + @Override + public void setSubtitle(int resId) { + mActionBar.setSubtitle(resId); + } + + @Override + public void setDisplayOptions(int options) { + mActionBar.setDisplayOptions(options); + } + + @Override + public void setDisplayOptions(int options, int mask) { + mActionBar.setDisplayOptions(options, mask); + } + + @Override + public void setDisplayUseLogoEnabled(boolean useLogo) { + mActionBar.setDisplayUseLogoEnabled(useLogo); + } + + @Override + public void setDisplayShowHomeEnabled(boolean showHome) { + mActionBar.setDisplayShowHomeEnabled(showHome); + } + + @Override + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + mActionBar.setDisplayHomeAsUpEnabled(showHomeAsUp); + } + + @Override + public void setDisplayShowTitleEnabled(boolean showTitle) { + mActionBar.setDisplayShowTitleEnabled(showTitle); + } + + @Override + public void setDisplayShowCustomEnabled(boolean showCustom) { + mActionBar.setDisplayShowCustomEnabled(showCustom); + } + + @Override + public void setBackgroundDrawable(Drawable d) { + mActionBar.setBackgroundDrawable(d); + } + + @Override + public void setStackedBackgroundDrawable(Drawable d) { + mActionBar.setStackedBackgroundDrawable(d); + } + + @Override + public void setSplitBackgroundDrawable(Drawable d) { + mActionBar.setSplitBackgroundDrawable(d); + } + + @Override + public View getCustomView() { + return mActionBar.getCustomView(); + } + + @Override + public CharSequence getTitle() { + return mActionBar.getTitle(); + } + + @Override + public CharSequence getSubtitle() { + return mActionBar.getSubtitle(); + } + + @Override + public int getNavigationMode() { + return mActionBar.getNavigationMode(); + } + + @Override + public void setNavigationMode(int mode) { + mActionBar.setNavigationMode(mode); + } + + @Override + public int getDisplayOptions() { + return mActionBar.getDisplayOptions(); + } + + public class TabWrapper extends ActionBar.Tab implements android.app.ActionBar.TabListener { + final android.app.ActionBar.Tab mNativeTab; + private Object mTag; + private TabListener mListener; + + public TabWrapper(android.app.ActionBar.Tab nativeTab) { + mNativeTab = nativeTab; + mNativeTab.setTag(this); + mNativeTab.setTabListener(this); + } + + @Override + public int getPosition() { + return mNativeTab.getPosition(); + } + + @Override + public Drawable getIcon() { + return mNativeTab.getIcon(); + } + + @Override + public CharSequence getText() { + return mNativeTab.getText(); + } + + @Override + public Tab setIcon(Drawable icon) { + mNativeTab.setIcon(icon); + return this; + } + + @Override + public Tab setIcon(int resId) { + mNativeTab.setIcon(resId); + return this; + } + + @Override + public Tab setText(CharSequence text) { + mNativeTab.setText(text); + return this; + } + + @Override + public Tab setText(int resId) { + mNativeTab.setText(resId); + return this; + } + + @Override + public Tab setCustomView(View view) { + mNativeTab.setCustomView(view); + return this; + } + + @Override + public Tab setCustomView(int layoutResId) { + mNativeTab.setCustomView(layoutResId); + return this; + } + + @Override + public View getCustomView() { + return mNativeTab.getCustomView(); + } + + @Override + public Tab setTag(Object obj) { + mTag = obj; + return this; + } + + @Override + public Object getTag() { + return mTag; + } + + @Override + public Tab setTabListener(TabListener listener) { + mListener = listener; + return this; + } + + @Override + public void select() { + mNativeTab.select(); + } + + @Override + public Tab setContentDescription(int resId) { + mNativeTab.setContentDescription(resId); + return this; + } + + @Override + public Tab setContentDescription(CharSequence contentDesc) { + mNativeTab.setContentDescription(contentDesc); + return this; + } + + @Override + public CharSequence getContentDescription() { + return mNativeTab.getContentDescription(); + } + + @Override + public void onTabReselected(android.app.ActionBar.Tab tab, android.app.FragmentTransaction ft) { + if (mListener != null) { + FragmentTransaction trans = null; + if (mActivity instanceof SherlockFragmentActivity) { + trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction() + .disallowAddToBackStack(); + } + + mListener.onTabReselected(this, trans); + + if (trans != null && !trans.isEmpty()) { + trans.commit(); + } + } + } + + @Override + public void onTabSelected(android.app.ActionBar.Tab tab, android.app.FragmentTransaction ft) { + if (mListener != null) { + mListener.onTabSelected(this, mFragmentTransaction); + + if (mFragmentTransaction != null) { + if (!mFragmentTransaction.isEmpty()) { + mFragmentTransaction.commit(); + } + mFragmentTransaction = null; + } + } + } + + @Override + public void onTabUnselected(android.app.ActionBar.Tab tab, android.app.FragmentTransaction ft) { + if (mListener != null) { + FragmentTransaction trans = null; + if (mActivity instanceof SherlockFragmentActivity) { + trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction() + .disallowAddToBackStack(); + mFragmentTransaction = trans; + } + + mListener.onTabUnselected(this, trans); + } + } + } + + @Override + public Tab newTab() { + return new TabWrapper(mActionBar.newTab()); + } + + @Override + public void addTab(Tab tab) { + mActionBar.addTab(((TabWrapper)tab).mNativeTab); + } + + @Override + public void addTab(Tab tab, boolean setSelected) { + mActionBar.addTab(((TabWrapper)tab).mNativeTab, setSelected); + } + + @Override + public void addTab(Tab tab, int position) { + mActionBar.addTab(((TabWrapper)tab).mNativeTab, position); + } + + @Override + public void addTab(Tab tab, int position, boolean setSelected) { + mActionBar.addTab(((TabWrapper)tab).mNativeTab, position, setSelected); + } + + @Override + public void removeTab(Tab tab) { + mActionBar.removeTab(((TabWrapper)tab).mNativeTab); + } + + @Override + public void removeTabAt(int position) { + mActionBar.removeTabAt(position); + } + + @Override + public void removeAllTabs() { + mActionBar.removeAllTabs(); + } + + @Override + public void selectTab(Tab tab) { + mActionBar.selectTab(((TabWrapper)tab).mNativeTab); + } + + @Override + public Tab getSelectedTab() { + android.app.ActionBar.Tab selected = mActionBar.getSelectedTab(); + return (selected != null) ? (Tab)selected.getTag() : null; + } + + @Override + public Tab getTabAt(int index) { + android.app.ActionBar.Tab selected = mActionBar.getTabAt(index); + return (selected != null) ? (Tab)selected.getTag() : null; + } + + @Override + public int getTabCount() { + return mActionBar.getTabCount(); + } + + @Override + public int getHeight() { + return mActionBar.getHeight(); + } + + @Override + public void show() { + mActionBar.show(); + } + + @Override + public void hide() { + mActionBar.hide(); + } + + @Override + public boolean isShowing() { + return mActionBar.isShowing(); + } + + @Override + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + mMenuVisibilityListeners.add(listener); + } + + @Override + public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + mMenuVisibilityListeners.remove(listener); + } + + @Override + public void onMenuVisibilityChanged(boolean isVisible) { + for (OnMenuVisibilityListener listener : mMenuVisibilityListeners) { + listener.onMenuVisibilityChanged(isVisible); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Animator.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Animator.java new file mode 100644 index 000000000..2caf5b4a9 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Animator.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import java.util.ArrayList; + +import android.view.animation.Interpolator; + +/** + * This is the superclass for classes which provide basic support for animations which can be + * started, ended, and have AnimatorListeners added to them. + */ +public abstract class Animator implements Cloneable { + + + /** + * The set of listeners to be sent events through the life of an animation. + */ + ArrayList mListeners = null; + + /** + * Starts this animation. If the animation has a nonzero startDelay, the animation will start + * running after that delay elapses. A non-delayed animation will have its initial + * value(s) set immediately, followed by calls to + * {@link AnimatorListener#onAnimationStart(Animator)} for any listeners of this animator. + * + *

The animation started by calling this method will be run on the thread that called + * this method. This thread should have a Looper on it (a runtime exception will be thrown if + * this is not the case). Also, if the animation will animate + * properties of objects in the view hierarchy, then the calling thread should be the UI + * thread for that view hierarchy.

+ * + */ + public void start() { + } + + /** + * Cancels the animation. Unlike {@link #end()}, cancel() causes the animation to + * stop in its tracks, sending an + * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to + * its listeners, followed by an + * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message. + * + *

This method must be called on the thread that is running the animation.

+ */ + public void cancel() { + } + + /** + * Ends the animation. This causes the animation to assign the end value of the property being + * animated, then calling the + * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on + * its listeners. + * + *

This method must be called on the thread that is running the animation.

+ */ + public void end() { + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + public abstract long getStartDelay(); + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + public abstract void setStartDelay(long startDelay); + + + /** + * Sets the length of the animation. + * + * @param duration The length of the animation, in milliseconds. + */ + public abstract Animator setDuration(long duration); + + /** + * Gets the length of the animation. + * + * @return The length of the animation, in milliseconds. + */ + public abstract long getDuration(); + + /** + * The time interpolator used in calculating the elapsed fraction of this animation. The + * interpolator determines whether the animation runs with linear or non-linear motion, + * such as acceleration and deceleration. The default value is + * {@link android.view.animation.AccelerateDecelerateInterpolator} + * + * @param value the interpolator to be used by this animation + */ + public abstract void setInterpolator(/*Time*/Interpolator value); + + /** + * Returns whether this Animator is currently running (having been started and gone past any + * initial startDelay period and not yet ended). + * + * @return Whether the Animator is running. + */ + public abstract boolean isRunning(); + + /** + * Returns whether this Animator has been started and not yet ended. This state is a superset + * of the state of {@link #isRunning()}, because an Animator with a nonzero + * {@link #getStartDelay() startDelay} will return true for {@link #isStarted()} during the + * delay phase, whereas {@link #isRunning()} will return true only after the delay phase + * is complete. + * + * @return Whether the Animator has been started and not yet ended. + */ + public boolean isStarted() { + // Default method returns value for isRunning(). Subclasses should override to return a + // real value. + return isRunning(); + } + + /** + * Adds a listener to the set of listeners that are sent events through the life of an + * animation, such as start, repeat, and end. + * + * @param listener the listener to be added to the current set of listeners for this animation. + */ + public void addListener(AnimatorListener listener) { + if (mListeners == null) { + mListeners = new ArrayList(); + } + mListeners.add(listener); + } + + /** + * Removes a listener from the set listening to this animation. + * + * @param listener the listener to be removed from the current set of listeners for this + * animation. + */ + public void removeListener(AnimatorListener listener) { + if (mListeners == null) { + return; + } + mListeners.remove(listener); + if (mListeners.size() == 0) { + mListeners = null; + } + } + + /** + * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently + * listening for events on this Animator object. + * + * @return ArrayList The set of listeners. + */ + public ArrayList getListeners() { + return mListeners; + } + + /** + * Removes all listeners from this object. This is equivalent to calling + * getListeners() followed by calling clear() on the + * returned list of listeners. + */ + public void removeAllListeners() { + if (mListeners != null) { + mListeners.clear(); + mListeners = null; + } + } + + @Override + public Animator clone() { + try { + final Animator anim = (Animator) super.clone(); + if (mListeners != null) { + ArrayList oldListeners = mListeners; + anim.mListeners = new ArrayList(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mListeners.add(oldListeners.get(i)); + } + } + return anim; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + /** + * This method tells the object to use appropriate information to extract + * starting values for the animation. For example, a AnimatorSet object will pass + * this call to its child objects to tell them to set up the values. A + * ObjectAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An ValueAnimator object will ignore the request since it does not have enough + * information (such as a target object) to gather these values. + */ + public void setupStartValues() { + } + + /** + * This method tells the object to use appropriate information to extract + * ending values for the animation. For example, a AnimatorSet object will pass + * this call to its child objects to tell them to set up the values. A + * ObjectAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An ValueAnimator object will ignore the request since it does not have enough + * information (such as a target object) to gather these values. + */ + public void setupEndValues() { + } + + /** + * Sets the target object whose property will be animated by this animation. Not all subclasses + * operate on target objects (for example, {@link ValueAnimator}, but this method + * is on the superclass for the convenience of dealing generically with those subclasses + * that do handle targets. + * + * @param target The object being animated + */ + public void setTarget(Object target) { + } + + /** + *

An animation listener receives notifications from an animation. + * Notifications indicate animation related events, such as the end or the + * repetition of the animation.

+ */ + public static interface AnimatorListener { + /** + *

Notifies the start of the animation.

+ * + * @param animation The started animation. + */ + void onAnimationStart(Animator animation); + + /** + *

Notifies the end of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.

+ * + * @param animation The animation which reached its end. + */ + void onAnimationEnd(Animator animation); + + /** + *

Notifies the cancellation of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.

+ * + * @param animation The animation which was canceled. + */ + void onAnimationCancel(Animator animation); + + /** + *

Notifies the repetition of the animation.

+ * + * @param animation The animation which was repeated. + */ + void onAnimationRepeat(Animator animation); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorListenerAdapter.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorListenerAdapter.java new file mode 100644 index 000000000..02ddff48d --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorListenerAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +/** + * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}. + * Any custom listener that cares only about a subset of the methods of this listener can + * simply subclass this adapter class instead of implementing the interface directly. + */ +public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener { + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationCancel(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationEnd(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationRepeat(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationStart(Animator animation) { + } + +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorSet.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorSet.java new file mode 100644 index 000000000..7de2352d4 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/AnimatorSet.java @@ -0,0 +1,1111 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import android.view.animation.Interpolator; + +/** + * This class plays a set of {@link Animator} objects in the specified order. Animations + * can be set up to play together, in sequence, or after a specified delay. + * + *

There are two different approaches to adding animations to a AnimatorSet: + * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or + * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add + * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be + * used in conjunction with methods in the {@link AnimatorSet.Builder Builder} + * class to add animations + * one by one.

+ * + *

It is possible to set up a AnimatorSet with circular dependencies between + * its animations. For example, an animation a1 could be set up to start before animation a2, a2 + * before a3, and a3 before a1. The results of this configuration are undefined, but will typically + * result in none of the affected animations being played. Because of this (and because + * circular dependencies do not make logical sense anyway), circular dependencies + * should be avoided, and the dependency flow of animations should only be in one direction. + */ +@SuppressWarnings("unchecked") +public final class AnimatorSet extends Animator { + + /** + * Internal variables + * NOTE: This object implements the clone() method, making a deep copy of any referenced + * objects. As other non-trivial fields are added to this class, make sure to add logic + * to clone() to make deep copies of them. + */ + + /** + * Tracks animations currently being played, so that we know what to + * cancel or end when cancel() or end() is called on this AnimatorSet + */ + private ArrayList mPlayingSet = new ArrayList(); + + /** + * Contains all nodes, mapped to their respective Animators. When new + * dependency information is added for an Animator, we want to add it + * to a single node representing that Animator, not create a new Node + * if one already exists. + */ + private HashMap mNodeMap = new HashMap(); + + /** + * Set of all nodes created for this AnimatorSet. This list is used upon + * starting the set, and the nodes are placed in sorted order into the + * sortedNodes collection. + */ + private ArrayList mNodes = new ArrayList(); + + /** + * The sorted list of nodes. This is the order in which the animations will + * be played. The details about when exactly they will be played depend + * on the dependency relationships of the nodes. + */ + private ArrayList mSortedNodes = new ArrayList(); + + /** + * Flag indicating whether the nodes should be sorted prior to playing. This + * flag allows us to cache the previous sorted nodes so that if the sequence + * is replayed with no changes, it does not have to re-sort the nodes again. + */ + private boolean mNeedsSort = true; + + private AnimatorSetListener mSetListener = null; + + /** + * Flag indicating that the AnimatorSet has been manually + * terminated (by calling cancel() or end()). + * This flag is used to avoid starting other animations when currently-playing + * child animations of this AnimatorSet end. It also determines whether cancel/end + * notifications are sent out via the normal AnimatorSetListener mechanism. + */ + boolean mTerminated = false; + + /** + * Indicates whether an AnimatorSet has been start()'d, whether or + * not there is a nonzero startDelay. + */ + private boolean mStarted = false; + + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + // Animator used for a nonzero startDelay + private ValueAnimator mDelayAnim = null; + + + // How long the child animations should last in ms. The default value is negative, which + // simply means that there is no duration set on the AnimatorSet. When a real duration is + // set, it is passed along to the child animations. + private long mDuration = -1; + + + /** + * Sets up this AnimatorSet to play all of the supplied animations at the same time. + * + * @param items The animations that will be started simultaneously. + */ + public void playTogether(Animator... items) { + if (items != null) { + mNeedsSort = true; + Builder builder = play(items[0]); + for (int i = 1; i < items.length; ++i) { + builder.with(items[i]); + } + } + } + + /** + * Sets up this AnimatorSet to play all of the supplied animations at the same time. + * + * @param items The animations that will be started simultaneously. + */ + public void playTogether(Collection items) { + if (items != null && items.size() > 0) { + mNeedsSort = true; + Builder builder = null; + for (Animator anim : items) { + if (builder == null) { + builder = play(anim); + } else { + builder.with(anim); + } + } + } + } + + /** + * Sets up this AnimatorSet to play each of the supplied animations when the + * previous animation ends. + * + * @param items The animations that will be started one after another. + */ + public void playSequentially(Animator... items) { + if (items != null) { + mNeedsSort = true; + if (items.length == 1) { + play(items[0]); + } else { + for (int i = 0; i < items.length - 1; ++i) { + play(items[i]).before(items[i+1]); + } + } + } + } + + /** + * Sets up this AnimatorSet to play each of the supplied animations when the + * previous animation ends. + * + * @param items The animations that will be started one after another. + */ + public void playSequentially(List items) { + if (items != null && items.size() > 0) { + mNeedsSort = true; + if (items.size() == 1) { + play(items.get(0)); + } else { + for (int i = 0; i < items.size() - 1; ++i) { + play(items.get(i)).before(items.get(i+1)); + } + } + } + } + + /** + * Returns the current list of child Animator objects controlled by this + * AnimatorSet. This is a copy of the internal list; modifications to the returned list + * will not affect the AnimatorSet, although changes to the underlying Animator objects + * will affect those objects being managed by the AnimatorSet. + * + * @return ArrayList The list of child animations of this AnimatorSet. + */ + public ArrayList getChildAnimations() { + ArrayList childList = new ArrayList(); + for (Node node : mNodes) { + childList.add(node.animation); + } + return childList; + } + + /** + * Sets the target object for all current {@link #getChildAnimations() child animations} + * of this AnimatorSet that take targets ({@link ObjectAnimator} and + * AnimatorSet). + * + * @param target The object being animated + */ + @Override + public void setTarget(Object target) { + for (Node node : mNodes) { + Animator animation = node.animation; + if (animation instanceof AnimatorSet) { + ((AnimatorSet)animation).setTarget(target); + } else if (animation instanceof ObjectAnimator) { + ((ObjectAnimator)animation).setTarget(target); + } + } + } + + /** + * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} + * of this AnimatorSet. + * + * @param interpolator the interpolator to be used by each child animation of this AnimatorSet + */ + @Override + public void setInterpolator(/*Time*/Interpolator interpolator) { + for (Node node : mNodes) { + node.animation.setInterpolator(interpolator); + } + } + + /** + * This method creates a Builder object, which is used to + * set up playing constraints. This initial play() method + * tells the Builder the animation that is the dependency for + * the succeeding commands to the Builder. For example, + * calling play(a1).with(a2) sets up the AnimatorSet to play + * a1 and a2 at the same time, + * play(a1).before(a2) sets up the AnimatorSet to play + * a1 first, followed by a2, and + * play(a1).after(a2) sets up the AnimatorSet to play + * a2 first, followed by a1. + * + *

Note that play() is the only way to tell the + * Builder the animation upon which the dependency is created, + * so successive calls to the various functions in Builder + * will all refer to the initial parameter supplied in play() + * as the dependency of the other animations. For example, calling + * play(a1).before(a2).before(a3) will play both a2 + * and a3 when a1 ends; it does not set up a dependency between + * a2 and a3.

+ * + * @param anim The animation that is the dependency used in later calls to the + * methods in the returned Builder object. A null parameter will result + * in a null Builder return value. + * @return Builder The object that constructs the AnimatorSet based on the dependencies + * outlined in the calls to play and the other methods in the + * BuilderNote that canceling a AnimatorSet also cancels all of the animations that it + * is responsible for.

+ */ + @Override + public void cancel() { + mTerminated = true; + if (isStarted()) { + ArrayList tmpListeners = null; + if (mListeners != null) { + tmpListeners = (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + if (mDelayAnim != null && mDelayAnim.isRunning()) { + // If we're currently in the startDelay period, just cancel that animator and + // send out the end event to all listeners + mDelayAnim.cancel(); + } else if (mSortedNodes.size() > 0) { + for (Node node : mSortedNodes) { + node.animation.cancel(); + } + } + if (tmpListeners != null) { + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationEnd(this); + } + } + mStarted = false; + } + } + + /** + * {@inheritDoc} + * + *

Note that ending a AnimatorSet also ends all of the animations that it is + * responsible for.

+ */ + @Override + public void end() { + mTerminated = true; + if (isStarted()) { + if (mSortedNodes.size() != mNodes.size()) { + // hasn't been started yet - sort the nodes now, then end them + sortNodes(); + for (Node node : mSortedNodes) { + if (mSetListener == null) { + mSetListener = new AnimatorSetListener(this); + } + node.animation.addListener(mSetListener); + } + } + if (mDelayAnim != null) { + mDelayAnim.cancel(); + } + if (mSortedNodes.size() > 0) { + for (Node node : mSortedNodes) { + node.animation.end(); + } + } + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationEnd(this); + } + } + mStarted = false; + } + } + + /** + * Returns true if any of the child animations of this AnimatorSet have been started and have + * not yet ended. + * @return Whether this AnimatorSet has been started and has not yet ended. + */ + @Override + public boolean isRunning() { + for (Node node : mNodes) { + if (node.animation.isRunning()) { + return true; + } + } + return false; + } + + @Override + public boolean isStarted() { + return mStarted; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + @Override + public long getStartDelay() { + return mStartDelay; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + @Override + public void setStartDelay(long startDelay) { + mStartDelay = startDelay; + } + + /** + * Gets the length of each of the child animations of this AnimatorSet. This value may + * be less than 0, which indicates that no duration has been set on this AnimatorSet + * and each of the child animations will use their own duration. + * + * @return The length of the animation, in milliseconds, of each of the child + * animations of this AnimatorSet. + */ + @Override + public long getDuration() { + return mDuration; + } + + /** + * Sets the length of each of the current child animations of this AnimatorSet. By default, + * each child animation will use its own duration. If the duration is set on the AnimatorSet, + * then each child animation inherits this duration. + * + * @param duration The length of the animation, in milliseconds, of each of the child + * animations of this AnimatorSet. + */ + @Override + public AnimatorSet setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("duration must be a value of zero or greater"); + } + for (Node node : mNodes) { + // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to + // insert "play-after" delays + node.animation.setDuration(duration); + } + mDuration = duration; + return this; + } + + @Override + public void setupStartValues() { + for (Node node : mNodes) { + node.animation.setupStartValues(); + } + } + + @Override + public void setupEndValues() { + for (Node node : mNodes) { + node.animation.setupEndValues(); + } + } + + /** + * {@inheritDoc} + * + *

Starting this AnimatorSet will, in turn, start the animations for which + * it is responsible. The details of when exactly those animations are started depends on + * the dependency relationships that have been set up between the animations. + */ + @Override + public void start() { + mTerminated = false; + mStarted = true; + + // First, sort the nodes (if necessary). This will ensure that sortedNodes + // contains the animation nodes in the correct order. + sortNodes(); + + int numSortedNodes = mSortedNodes.size(); + for (int i = 0; i < numSortedNodes; ++i) { + Node node = mSortedNodes.get(i); + // First, clear out the old listeners + ArrayList oldListeners = node.animation.getListeners(); + if (oldListeners != null && oldListeners.size() > 0) { + final ArrayList clonedListeners = new + ArrayList(oldListeners); + + for (AnimatorListener listener : clonedListeners) { + if (listener instanceof DependencyListener || + listener instanceof AnimatorSetListener) { + node.animation.removeListener(listener); + } + } + } + } + + // nodesToStart holds the list of nodes to be started immediately. We don't want to + // start the animations in the loop directly because we first need to set up + // dependencies on all of the nodes. For example, we don't want to start an animation + // when some other animation also wants to start when the first animation begins. + final ArrayList nodesToStart = new ArrayList(); + for (int i = 0; i < numSortedNodes; ++i) { + Node node = mSortedNodes.get(i); + if (mSetListener == null) { + mSetListener = new AnimatorSetListener(this); + } + if (node.dependencies == null || node.dependencies.size() == 0) { + nodesToStart.add(node); + } else { + int numDependencies = node.dependencies.size(); + for (int j = 0; j < numDependencies; ++j) { + Dependency dependency = node.dependencies.get(j); + dependency.node.animation.addListener( + new DependencyListener(this, node, dependency.rule)); + } + node.tmpDependencies = (ArrayList) node.dependencies.clone(); + } + node.animation.addListener(mSetListener); + } + // Now that all dependencies are set up, start the animations that should be started. + if (mStartDelay <= 0) { + for (Node node : nodesToStart) { + node.animation.start(); + mPlayingSet.add(node.animation); + } + } else { + mDelayAnim = ValueAnimator.ofFloat(0f, 1f); + mDelayAnim.setDuration(mStartDelay); + mDelayAnim.addListener(new AnimatorListenerAdapter() { + boolean canceled = false; + public void onAnimationCancel(Animator anim) { + canceled = true; + } + public void onAnimationEnd(Animator anim) { + if (!canceled) { + int numNodes = nodesToStart.size(); + for (int i = 0; i < numNodes; ++i) { + Node node = nodesToStart.get(i); + node.animation.start(); + mPlayingSet.add(node.animation); + } + } + } + }); + mDelayAnim.start(); + } + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this); + } + } + if (mNodes.size() == 0 && mStartDelay == 0) { + // Handle unusual case where empty AnimatorSet is started - should send out + // end event immediately since the event will not be sent out at all otherwise + mStarted = false; + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(this); + } + } + } + } + + @Override + public AnimatorSet clone() { + final AnimatorSet anim = (AnimatorSet) super.clone(); + /* + * The basic clone() operation copies all items. This doesn't work very well for + * AnimatorSet, because it will copy references that need to be recreated and state + * that may not apply. What we need to do now is put the clone in an uninitialized + * state, with fresh, empty data structures. Then we will build up the nodes list + * manually, as we clone each Node (and its animation). The clone will then be sorted, + * and will populate any appropriate lists, when it is started. + */ + anim.mNeedsSort = true; + anim.mTerminated = false; + anim.mStarted = false; + anim.mPlayingSet = new ArrayList(); + anim.mNodeMap = new HashMap(); + anim.mNodes = new ArrayList(); + anim.mSortedNodes = new ArrayList(); + + // Walk through the old nodes list, cloning each node and adding it to the new nodemap. + // One problem is that the old node dependencies point to nodes in the old AnimatorSet. + // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. + HashMap nodeCloneMap = new HashMap(); // + for (Node node : mNodes) { + Node nodeClone = node.clone(); + nodeCloneMap.put(node, nodeClone); + anim.mNodes.add(nodeClone); + anim.mNodeMap.put(nodeClone.animation, nodeClone); + // Clear out the dependencies in the clone; we'll set these up manually later + nodeClone.dependencies = null; + nodeClone.tmpDependencies = null; + nodeClone.nodeDependents = null; + nodeClone.nodeDependencies = null; + // clear out any listeners that were set up by the AnimatorSet; these will + // be set up when the clone's nodes are sorted + ArrayList cloneListeners = nodeClone.animation.getListeners(); + if (cloneListeners != null) { + ArrayList listenersToRemove = null; + for (AnimatorListener listener : cloneListeners) { + if (listener instanceof AnimatorSetListener) { + if (listenersToRemove == null) { + listenersToRemove = new ArrayList(); + } + listenersToRemove.add(listener); + } + } + if (listenersToRemove != null) { + for (AnimatorListener listener : listenersToRemove) { + cloneListeners.remove(listener); + } + } + } + } + // Now that we've cloned all of the nodes, we're ready to walk through their + // dependencies, mapping the old dependencies to the new nodes + for (Node node : mNodes) { + Node nodeClone = nodeCloneMap.get(node); + if (node.dependencies != null) { + for (Dependency dependency : node.dependencies) { + Node clonedDependencyNode = nodeCloneMap.get(dependency.node); + Dependency cloneDependency = new Dependency(clonedDependencyNode, + dependency.rule); + nodeClone.addDependency(cloneDependency); + } + } + } + + return anim; + } + + /** + * This class is the mechanism by which animations are started based on events in other + * animations. If an animation has multiple dependencies on other animations, then + * all dependencies must be satisfied before the animation is started. + */ + private static class DependencyListener implements AnimatorListener { + + private AnimatorSet mAnimatorSet; + + // The node upon which the dependency is based. + private Node mNode; + + // The Dependency rule (WITH or AFTER) that the listener should wait for on + // the node + private int mRule; + + public DependencyListener(AnimatorSet animatorSet, Node node, int rule) { + this.mAnimatorSet = animatorSet; + this.mNode = node; + this.mRule = rule; + } + + /** + * Ignore cancel events for now. We may want to handle this eventually, + * to prevent follow-on animations from running when some dependency + * animation is canceled. + */ + public void onAnimationCancel(Animator animation) { + } + + /** + * An end event is received - see if this is an event we are listening for + */ + public void onAnimationEnd(Animator animation) { + if (mRule == Dependency.AFTER) { + startIfReady(animation); + } + } + + /** + * Ignore repeat events for now + */ + public void onAnimationRepeat(Animator animation) { + } + + /** + * A start event is received - see if this is an event we are listening for + */ + public void onAnimationStart(Animator animation) { + if (mRule == Dependency.WITH) { + startIfReady(animation); + } + } + + /** + * Check whether the event received is one that the node was waiting for. + * If so, mark it as complete and see whether it's time to start + * the animation. + * @param dependencyAnimation the animation that sent the event. + */ + private void startIfReady(Animator dependencyAnimation) { + if (mAnimatorSet.mTerminated) { + // if the parent AnimatorSet was canceled, then don't start any dependent anims + return; + } + Dependency dependencyToRemove = null; + int numDependencies = mNode.tmpDependencies.size(); + for (int i = 0; i < numDependencies; ++i) { + Dependency dependency = mNode.tmpDependencies.get(i); + if (dependency.rule == mRule && + dependency.node.animation == dependencyAnimation) { + // rule fired - remove the dependency and listener and check to + // see whether it's time to start the animation + dependencyToRemove = dependency; + dependencyAnimation.removeListener(this); + break; + } + } + mNode.tmpDependencies.remove(dependencyToRemove); + if (mNode.tmpDependencies.size() == 0) { + // all dependencies satisfied: start the animation + mNode.animation.start(); + mAnimatorSet.mPlayingSet.add(mNode.animation); + } + } + + } + + private class AnimatorSetListener implements AnimatorListener { + + private AnimatorSet mAnimatorSet; + + AnimatorSetListener(AnimatorSet animatorSet) { + mAnimatorSet = animatorSet; + } + + public void onAnimationCancel(Animator animation) { + if (!mTerminated) { + // Listeners are already notified of the AnimatorSet canceling in cancel(). + // The logic below only kicks in when animations end normally + if (mPlayingSet.size() == 0) { + if (mListeners != null) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mListeners.get(i).onAnimationCancel(mAnimatorSet); + } + } + } + } + } + + public void onAnimationEnd(Animator animation) { + animation.removeListener(this); + mPlayingSet.remove(animation); + Node animNode = mAnimatorSet.mNodeMap.get(animation); + animNode.done = true; + if (!mTerminated) { + // Listeners are already notified of the AnimatorSet ending in cancel() or + // end(); the logic below only kicks in when animations end normally + ArrayList sortedNodes = mAnimatorSet.mSortedNodes; + boolean allDone = true; + int numSortedNodes = sortedNodes.size(); + for (int i = 0; i < numSortedNodes; ++i) { + if (!sortedNodes.get(i).done) { + allDone = false; + break; + } + } + if (allDone) { + // If this was the last child animation to end, then notify listeners that this + // AnimatorSet has ended + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(mAnimatorSet); + } + } + mAnimatorSet.mStarted = false; + } + } + } + + // Nothing to do + public void onAnimationRepeat(Animator animation) { + } + + // Nothing to do + public void onAnimationStart(Animator animation) { + } + + } + + /** + * This method sorts the current set of nodes, if needed. The sort is a simple + * DependencyGraph sort, which goes like this: + * - All nodes without dependencies become 'roots' + * - while roots list is not null + * - for each root r + * - add r to sorted list + * - remove r as a dependency from any other node + * - any nodes with no dependencies are added to the roots list + */ + private void sortNodes() { + if (mNeedsSort) { + mSortedNodes.clear(); + ArrayList roots = new ArrayList(); + int numNodes = mNodes.size(); + for (int i = 0; i < numNodes; ++i) { + Node node = mNodes.get(i); + if (node.dependencies == null || node.dependencies.size() == 0) { + roots.add(node); + } + } + ArrayList tmpRoots = new ArrayList(); + while (roots.size() > 0) { + int numRoots = roots.size(); + for (int i = 0; i < numRoots; ++i) { + Node root = roots.get(i); + mSortedNodes.add(root); + if (root.nodeDependents != null) { + int numDependents = root.nodeDependents.size(); + for (int j = 0; j < numDependents; ++j) { + Node node = root.nodeDependents.get(j); + node.nodeDependencies.remove(root); + if (node.nodeDependencies.size() == 0) { + tmpRoots.add(node); + } + } + } + } + roots.clear(); + roots.addAll(tmpRoots); + tmpRoots.clear(); + } + mNeedsSort = false; + if (mSortedNodes.size() != mNodes.size()) { + throw new IllegalStateException("Circular dependencies cannot exist" + + " in AnimatorSet"); + } + } else { + // Doesn't need sorting, but still need to add in the nodeDependencies list + // because these get removed as the event listeners fire and the dependencies + // are satisfied + int numNodes = mNodes.size(); + for (int i = 0; i < numNodes; ++i) { + Node node = mNodes.get(i); + if (node.dependencies != null && node.dependencies.size() > 0) { + int numDependencies = node.dependencies.size(); + for (int j = 0; j < numDependencies; ++j) { + Dependency dependency = node.dependencies.get(j); + if (node.nodeDependencies == null) { + node.nodeDependencies = new ArrayList(); + } + if (!node.nodeDependencies.contains(dependency.node)) { + node.nodeDependencies.add(dependency.node); + } + } + } + // nodes are 'done' by default; they become un-done when started, and done + // again when ended + node.done = false; + } + } + } + + /** + * Dependency holds information about the node that some other node is + * dependent upon and the nature of that dependency. + * + */ + private static class Dependency { + static final int WITH = 0; // dependent node must start with this dependency node + static final int AFTER = 1; // dependent node must start when this dependency node finishes + + // The node that the other node with this Dependency is dependent upon + public Node node; + + // The nature of the dependency (WITH or AFTER) + public int rule; + + public Dependency(Node node, int rule) { + this.node = node; + this.rule = rule; + } + } + + /** + * A Node is an embodiment of both the Animator that it wraps as well as + * any dependencies that are associated with that Animation. This includes + * both dependencies upon other nodes (in the dependencies list) as + * well as dependencies of other nodes upon this (in the nodeDependents list). + */ + private static class Node implements Cloneable { + public Animator animation; + + /** + * These are the dependencies that this node's animation has on other + * nodes. For example, if this node's animation should begin with some + * other animation ends, then there will be an item in this node's + * dependencies list for that other animation's node. + */ + public ArrayList dependencies = null; + + /** + * tmpDependencies is a runtime detail. We use the dependencies list for sorting. + * But we also use the list to keep track of when multiple dependencies are satisfied, + * but removing each dependency as it is satisfied. We do not want to remove + * the dependency itself from the list, because we need to retain that information + * if the AnimatorSet is launched in the future. So we create a copy of the dependency + * list when the AnimatorSet starts and use this tmpDependencies list to track the + * list of satisfied dependencies. + */ + public ArrayList tmpDependencies = null; + + /** + * nodeDependencies is just a list of the nodes that this Node is dependent upon. + * This information is used in sortNodes(), to determine when a node is a root. + */ + public ArrayList nodeDependencies = null; + + /** + * nodeDepdendents is the list of nodes that have this node as a dependency. This + * is a utility field used in sortNodes to facilitate removing this node as a + * dependency when it is a root node. + */ + public ArrayList nodeDependents = null; + + /** + * Flag indicating whether the animation in this node is finished. This flag + * is used by AnimatorSet to check, as each animation ends, whether all child animations + * are done and it's time to send out an end event for the entire AnimatorSet. + */ + public boolean done = false; + + /** + * Constructs the Node with the animation that it encapsulates. A Node has no + * dependencies by default; dependencies are added via the addDependency() + * method. + * + * @param animation The animation that the Node encapsulates. + */ + public Node(Animator animation) { + this.animation = animation; + } + + /** + * Add a dependency to this Node. The dependency includes information about the + * node that this node is dependency upon and the nature of the dependency. + * @param dependency + */ + public void addDependency(Dependency dependency) { + if (dependencies == null) { + dependencies = new ArrayList(); + nodeDependencies = new ArrayList(); + } + dependencies.add(dependency); + if (!nodeDependencies.contains(dependency.node)) { + nodeDependencies.add(dependency.node); + } + Node dependencyNode = dependency.node; + if (dependencyNode.nodeDependents == null) { + dependencyNode.nodeDependents = new ArrayList(); + } + dependencyNode.nodeDependents.add(this); + } + + @Override + public Node clone() { + try { + Node node = (Node) super.clone(); + node.animation = (Animator) animation.clone(); + return node; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + } + + /** + * The Builder object is a utility class to facilitate adding animations to a + * AnimatorSet along with the relationships between the various animations. The + * intention of the Builder methods, along with the {@link + * AnimatorSet#play(Animator) play()} method of AnimatorSet is to make it possible + * to express the dependency relationships of animations in a natural way. Developers can also + * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link + * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need, + * but it might be easier in some situations to express the AnimatorSet of animations in pairs. + *

+ *

The Builder object cannot be constructed directly, but is rather constructed + * internally via a call to {@link AnimatorSet#play(Animator)}.

+ *

+ *

For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to + * play when anim2 finishes, and anim4 to play when anim3 finishes:

+ *
+     *     AnimatorSet s = new AnimatorSet();
+     *     s.play(anim1).with(anim2);
+     *     s.play(anim2).before(anim3);
+     *     s.play(anim4).after(anim3);
+     * 
+ *

+ *

Note in the example that both {@link Builder#before(Animator)} and {@link + * Builder#after(Animator)} are used. These are just different ways of expressing the same + * relationship and are provided to make it easier to say things in a way that is more natural, + * depending on the situation.

+ *

+ *

It is possible to make several calls into the same Builder object to express + * multiple relationships. However, note that it is only the animation passed into the initial + * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive + * calls to the Builder object. For example, the following code starts both anim2 + * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and + * anim3: + *

+     *   AnimatorSet s = new AnimatorSet();
+     *   s.play(anim1).before(anim2).before(anim3);
+     * 
+ * If the desired result is to play anim1 then anim2 then anim3, this code expresses the + * relationship correctly:

+ *
+     *   AnimatorSet s = new AnimatorSet();
+     *   s.play(anim1).before(anim2);
+     *   s.play(anim2).before(anim3);
+     * 
+ *

+ *

Note that it is possible to express relationships that cannot be resolved and will not + * result in sensible results. For example, play(anim1).after(anim1) makes no + * sense. In general, circular dependencies like this one (or more indirect ones where a depends + * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets + * that can boil down to a simple, one-way relationship of animations starting with, before, and + * after other, different, animations.

+ */ + public class Builder { + + /** + * This tracks the current node being processed. It is supplied to the play() method + * of AnimatorSet and passed into the constructor of Builder. + */ + private Node mCurrentNode; + + /** + * package-private constructor. Builders are only constructed by AnimatorSet, when the + * play() method is called. + * + * @param anim The animation that is the dependency for the other animations passed into + * the other methods of this Builder object. + */ + Builder(Animator anim) { + mCurrentNode = mNodeMap.get(anim); + if (mCurrentNode == null) { + mCurrentNode = new Node(anim); + mNodeMap.put(anim, mCurrentNode); + mNodes.add(mCurrentNode); + } + } + + /** + * Sets up the given animation to play at the same time as the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object. + * + * @param anim The animation that will play when the animation supplied to the + * {@link AnimatorSet#play(Animator)} method starts. + */ + public Builder with(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); + node.addDependency(dependency); + return this; + } + + /** + * Sets up the given animation to play when the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object + * ends. + * + * @param anim The animation that will play when the animation supplied to the + * {@link AnimatorSet#play(Animator)} method ends. + */ + public Builder before(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); + node.addDependency(dependency); + return this; + } + + /** + * Sets up the given animation to play when the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object + * to start when the animation supplied in this method call ends. + * + * @param anim The animation whose end will cause the animation supplied to the + * {@link AnimatorSet#play(Animator)} method to play. + */ + public Builder after(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(node, Dependency.AFTER); + mCurrentNode.addDependency(dependency); + return this; + } + + /** + * Sets up the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object + * to play when the given amount of time elapses. + * + * @param delay The number of milliseconds that should elapse before the + * animation starts. + */ + public Builder after(long delay) { + // setup dummy ValueAnimator just to run the clock + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(delay); + after(anim); + return this; + } + + } + +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatEvaluator.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatEvaluator.java new file mode 100644 index 000000000..e41019364 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatEvaluator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +/** + * This evaluator can be used to perform type interpolation between float values. + */ +public class FloatEvaluator implements TypeEvaluator { + + /** + * This function returns the result of linearly interpolating the start and end values, with + * fraction representing the proportion between the start and end values. The + * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0), + * where x0 is startValue, x1 is endValue, + * and t is fraction. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value; should be of type float or + * Float + * @param endValue The end value; should be of type float or Float + * @return A linear interpolation between the start and end values, given the + * fraction parameter. + */ + public Float evaluate(float fraction, Number startValue, Number endValue) { + float startFloat = startValue.floatValue(); + return startFloat + fraction * (endValue.floatValue() - startFloat); + } +} \ No newline at end of file diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatKeyframeSet.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatKeyframeSet.java new file mode 100644 index 000000000..6d9dafa7a --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/FloatKeyframeSet.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import java.util.ArrayList; +import android.view.animation.Interpolator; + +import com.actionbarsherlock.internal.nineoldandroids.animation.Keyframe.FloatKeyframe; + +/** + * This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + * + *

This type-specific subclass of KeyframeSet, along with the other type-specific subclass for + * int, exists to speed up the getValue() method when there is no custom + * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the + * Object equivalents of these primitive types.

+ */ +@SuppressWarnings("unchecked") +class FloatKeyframeSet extends KeyframeSet { + private float firstValue; + private float lastValue; + private float deltaValue; + private boolean firstTime = true; + + public FloatKeyframeSet(FloatKeyframe... keyframes) { + super(keyframes); + } + + @Override + public Object getValue(float fraction) { + return getFloatValue(fraction); + } + + @Override + public FloatKeyframeSet clone() { + ArrayList keyframes = mKeyframes; + int numKeyframes = mKeyframes.size(); + FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone(); + } + FloatKeyframeSet newSet = new FloatKeyframeSet(newKeyframes); + return newSet; + } + + public float getFloatValue(float fraction) { + if (mNumKeyframes == 2) { + if (firstTime) { + firstTime = false; + firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue(); + lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue(); + deltaValue = lastValue - firstValue; + } + if (mInterpolator != null) { + fraction = mInterpolator.getInterpolation(fraction); + } + if (mEvaluator == null) { + return firstValue + fraction * deltaValue; + } else { + return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue(); + } + } + if (fraction <= 0f) { + final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0); + final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1); + float prevValue = prevKeyframe.getFloatValue(); + float nextValue = nextKeyframe.getFloatValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + intervalFraction * (nextValue - prevValue) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + floatValue(); + } else if (fraction >= 1f) { + final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2); + final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1); + float prevValue = prevKeyframe.getFloatValue(); + float nextValue = nextKeyframe.getFloatValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + intervalFraction * (nextValue - prevValue) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + floatValue(); + } + FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0); + for (int i = 1; i < mNumKeyframes; ++i) { + FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + float prevValue = prevKeyframe.getFloatValue(); + float nextValue = nextKeyframe.getFloatValue(); + return mEvaluator == null ? + prevValue + intervalFraction * (nextValue - prevValue) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + floatValue(); + } + prevKeyframe = nextKeyframe; + } + // shouldn't get here + return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue(); + } + +} + diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntEvaluator.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntEvaluator.java new file mode 100644 index 000000000..ed5e79ec6 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntEvaluator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +/** + * This evaluator can be used to perform type interpolation between int values. + */ +public class IntEvaluator implements TypeEvaluator { + + /** + * This function returns the result of linearly interpolating the start and end values, with + * fraction representing the proportion between the start and end values. The + * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0), + * where x0 is startValue, x1 is endValue, + * and t is fraction. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value; should be of type int or + * Integer + * @param endValue The end value; should be of type int or Integer + * @return A linear interpolation between the start and end values, given the + * fraction parameter. + */ + public Integer evaluate(float fraction, Integer startValue, Integer endValue) { + int startInt = startValue; + return (int)(startInt + fraction * (endValue - startInt)); + } +} \ No newline at end of file diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntKeyframeSet.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntKeyframeSet.java new file mode 100644 index 000000000..e9215e7f8 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/IntKeyframeSet.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import java.util.ArrayList; +import android.view.animation.Interpolator; + +import com.actionbarsherlock.internal.nineoldandroids.animation.Keyframe.IntKeyframe; + +/** + * This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + * + *

This type-specific subclass of KeyframeSet, along with the other type-specific subclass for + * float, exists to speed up the getValue() method when there is no custom + * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the + * Object equivalents of these primitive types.

+ */ +@SuppressWarnings("unchecked") +class IntKeyframeSet extends KeyframeSet { + private int firstValue; + private int lastValue; + private int deltaValue; + private boolean firstTime = true; + + public IntKeyframeSet(IntKeyframe... keyframes) { + super(keyframes); + } + + @Override + public Object getValue(float fraction) { + return getIntValue(fraction); + } + + @Override + public IntKeyframeSet clone() { + ArrayList keyframes = mKeyframes; + int numKeyframes = mKeyframes.size(); + IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone(); + } + IntKeyframeSet newSet = new IntKeyframeSet(newKeyframes); + return newSet; + } + + public int getIntValue(float fraction) { + if (mNumKeyframes == 2) { + if (firstTime) { + firstTime = false; + firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue(); + lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue(); + deltaValue = lastValue - firstValue; + } + if (mInterpolator != null) { + fraction = mInterpolator.getInterpolation(fraction); + } + if (mEvaluator == null) { + return firstValue + (int)(fraction * deltaValue); + } else { + return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue(); + } + } + if (fraction <= 0f) { + final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0); + final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1); + int prevValue = prevKeyframe.getIntValue(); + int nextValue = nextKeyframe.getIntValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + (int)(intervalFraction * (nextValue - prevValue)) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + intValue(); + } else if (fraction >= 1f) { + final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2); + final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1); + int prevValue = prevKeyframe.getIntValue(); + int nextValue = nextKeyframe.getIntValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + (int)(intervalFraction * (nextValue - prevValue)) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue(); + } + IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0); + for (int i = 1; i < mNumKeyframes; ++i) { + IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + int prevValue = prevKeyframe.getIntValue(); + int nextValue = nextKeyframe.getIntValue(); + return mEvaluator == null ? + prevValue + (int)(intervalFraction * (nextValue - prevValue)) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + intValue(); + } + prevKeyframe = nextKeyframe; + } + // shouldn't get here + return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue(); + } + +} + diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Keyframe.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Keyframe.java new file mode 100644 index 000000000..ab76fa7f6 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/Keyframe.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import android.view.animation.Interpolator; + +/** + * This class holds a time/value pair for an animation. The Keyframe class is used + * by {@link ValueAnimator} to define the values that the animation target will have over the course + * of the animation. As the time proceeds from one keyframe to the other, the value of the + * target object will animate between the value at the previous keyframe and the value at the + * next keyframe. Each keyframe also holds an optional {@link TimeInterpolator} + * object, which defines the time interpolation over the intervalue preceding the keyframe. + * + *

The Keyframe class itself is abstract. The type-specific factory methods will return + * a subclass of Keyframe specific to the type of value being stored. This is done to improve + * performance when dealing with the most common cases (e.g., float and + * int values). Other types will fall into a more general Keyframe class that + * treats its values as Objects. Unless your animation requires dealing with a custom type + * or a data structure that needs to be animated directly (and evaluated using an implementation + * of {@link TypeEvaluator}), you should stick to using float and int as animations using those + * types have lower runtime overhead than other types.

+ */ +@SuppressWarnings("rawtypes") +public abstract class Keyframe implements Cloneable { + /** + * The time at which mValue will hold true. + */ + float mFraction; + + /** + * The type of the value in this Keyframe. This type is determined at construction time, + * based on the type of the value object passed into the constructor. + */ + Class mValueType; + + /** + * The optional time interpolator for the interval preceding this keyframe. A null interpolator + * (the default) results in linear interpolation over the interval. + */ + private /*Time*/Interpolator mInterpolator = null; + + /** + * Flag to indicate whether this keyframe has a valid value. This flag is used when an + * animation first starts, to populate placeholder keyframes with real values derived + * from the target object. + */ + boolean mHasValue = false; + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public static Keyframe ofInt(float fraction, int value) { + return new IntKeyframe(fraction, value); + } + + /** + * Constructs a Keyframe object with the given time. The value at this time will be derived + * from the target object when the animation first starts (note that this implies that keyframes + * with no initial value must be used as part of an {@link ObjectAnimator}). + * The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + */ + public static Keyframe ofInt(float fraction) { + return new IntKeyframe(fraction); + } + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public static Keyframe ofFloat(float fraction, float value) { + return new FloatKeyframe(fraction, value); + } + + /** + * Constructs a Keyframe object with the given time. The value at this time will be derived + * from the target object when the animation first starts (note that this implies that keyframes + * with no initial value must be used as part of an {@link ObjectAnimator}). + * The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + */ + public static Keyframe ofFloat(float fraction) { + return new FloatKeyframe(fraction); + } + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public static Keyframe ofObject(float fraction, Object value) { + return new ObjectKeyframe(fraction, value); + } + + /** + * Constructs a Keyframe object with the given time. The value at this time will be derived + * from the target object when the animation first starts (note that this implies that keyframes + * with no initial value must be used as part of an {@link ObjectAnimator}). + * The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + */ + public static Keyframe ofObject(float fraction) { + return new ObjectKeyframe(fraction, null); + } + + /** + * Indicates whether this keyframe has a valid value. This method is called internally when + * an {@link ObjectAnimator} first starts; keyframes without values are assigned values at + * that time by deriving the value for the property from the target object. + * + * @return boolean Whether this object has a value assigned. + */ + public boolean hasValue() { + return mHasValue; + } + + /** + * Gets the value for this Keyframe. + * + * @return The value for this Keyframe. + */ + public abstract Object getValue(); + + /** + * Sets the value for this Keyframe. + * + * @param value value for this Keyframe. + */ + public abstract void setValue(Object value); + + /** + * Gets the time for this keyframe, as a fraction of the overall animation duration. + * + * @return The time associated with this keyframe, as a fraction of the overall animation + * duration. This should be a value between 0 and 1. + */ + public float getFraction() { + return mFraction; + } + + /** + * Sets the time for this keyframe, as a fraction of the overall animation duration. + * + * @param fraction time associated with this keyframe, as a fraction of the overall animation + * duration. This should be a value between 0 and 1. + */ + public void setFraction(float fraction) { + mFraction = fraction; + } + + /** + * Gets the optional interpolator for this Keyframe. A value of null indicates + * that there is no interpolation, which is the same as linear interpolation. + * + * @return The optional interpolator for this Keyframe. + */ + public /*Time*/Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * Sets the optional interpolator for this Keyframe. A value of null indicates + * that there is no interpolation, which is the same as linear interpolation. + * + * @return The optional interpolator for this Keyframe. + */ + public void setInterpolator(/*Time*/Interpolator interpolator) { + mInterpolator = interpolator; + } + + /** + * Gets the type of keyframe. This information is used by ValueAnimator to determine the type of + * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based + * on the type of Keyframe created. + * + * @return The type of the value stored in the Keyframe. + */ + public Class getType() { + return mValueType; + } + + @Override + public abstract Keyframe clone(); + + /** + * This internal subclass is used for all types which are not int or float. + */ + static class ObjectKeyframe extends Keyframe { + + /** + * The value of the animation at the time mFraction. + */ + Object mValue; + + ObjectKeyframe(float fraction, Object value) { + mFraction = fraction; + mValue = value; + mHasValue = (value != null); + mValueType = mHasValue ? value.getClass() : Object.class; + } + + public Object getValue() { + return mValue; + } + + public void setValue(Object value) { + mValue = value; + mHasValue = (value != null); + } + + @Override + public ObjectKeyframe clone() { + ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mValue); + kfClone.setInterpolator(getInterpolator()); + return kfClone; + } + } + + /** + * Internal subclass used when the keyframe value is of type int. + */ + static class IntKeyframe extends Keyframe { + + /** + * The value of the animation at the time mFraction. + */ + int mValue; + + IntKeyframe(float fraction, int value) { + mFraction = fraction; + mValue = value; + mValueType = int.class; + mHasValue = true; + } + + IntKeyframe(float fraction) { + mFraction = fraction; + mValueType = int.class; + } + + public int getIntValue() { + return mValue; + } + + public Object getValue() { + return mValue; + } + + public void setValue(Object value) { + if (value != null && value.getClass() == Integer.class) { + mValue = ((Integer)value).intValue(); + mHasValue = true; + } + } + + @Override + public IntKeyframe clone() { + IntKeyframe kfClone = new IntKeyframe(getFraction(), mValue); + kfClone.setInterpolator(getInterpolator()); + return kfClone; + } + } + + /** + * Internal subclass used when the keyframe value is of type float. + */ + static class FloatKeyframe extends Keyframe { + /** + * The value of the animation at the time mFraction. + */ + float mValue; + + FloatKeyframe(float fraction, float value) { + mFraction = fraction; + mValue = value; + mValueType = float.class; + mHasValue = true; + } + + FloatKeyframe(float fraction) { + mFraction = fraction; + mValueType = float.class; + } + + public float getFloatValue() { + return mValue; + } + + public Object getValue() { + return mValue; + } + + public void setValue(Object value) { + if (value != null && value.getClass() == Float.class) { + mValue = ((Float)value).floatValue(); + mHasValue = true; + } + } + + @Override + public FloatKeyframe clone() { + FloatKeyframe kfClone = new FloatKeyframe(getFraction(), mValue); + kfClone.setInterpolator(getInterpolator()); + return kfClone; + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/KeyframeSet.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/KeyframeSet.java new file mode 100644 index 000000000..a71e1ad3c --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/KeyframeSet.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import java.util.ArrayList; +import java.util.Arrays; +import android.view.animation.Interpolator; + +import com.actionbarsherlock.internal.nineoldandroids.animation.Keyframe.FloatKeyframe; +import com.actionbarsherlock.internal.nineoldandroids.animation.Keyframe.IntKeyframe; +import com.actionbarsherlock.internal.nineoldandroids.animation.Keyframe.ObjectKeyframe; + +/** + * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +class KeyframeSet { + + int mNumKeyframes; + + Keyframe mFirstKeyframe; + Keyframe mLastKeyframe; + /*Time*/Interpolator mInterpolator; // only used in the 2-keyframe case + ArrayList mKeyframes; // only used when there are not 2 keyframes + TypeEvaluator mEvaluator; + + + public KeyframeSet(Keyframe... keyframes) { + mNumKeyframes = keyframes.length; + mKeyframes = new ArrayList(); + mKeyframes.addAll(Arrays.asList(keyframes)); + mFirstKeyframe = mKeyframes.get(0); + mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); + mInterpolator = mLastKeyframe.getInterpolator(); + } + + public static KeyframeSet ofInt(int... values) { + int numKeyframes = values.length; + IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)]; + if (numKeyframes == 1) { + keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f); + keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]); + } else { + keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]); + for (int i = 1; i < numKeyframes; ++i) { + keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]); + } + } + return new IntKeyframeSet(keyframes); + } + + public static KeyframeSet ofFloat(float... values) { + int numKeyframes = values.length; + FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)]; + if (numKeyframes == 1) { + keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f); + keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]); + } else { + keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]); + for (int i = 1; i < numKeyframes; ++i) { + keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]); + } + } + return new FloatKeyframeSet(keyframes); + } + + public static KeyframeSet ofKeyframe(Keyframe... keyframes) { + // if all keyframes of same primitive type, create the appropriate KeyframeSet + int numKeyframes = keyframes.length; + boolean hasFloat = false; + boolean hasInt = false; + boolean hasOther = false; + for (int i = 0; i < numKeyframes; ++i) { + if (keyframes[i] instanceof FloatKeyframe) { + hasFloat = true; + } else if (keyframes[i] instanceof IntKeyframe) { + hasInt = true; + } else { + hasOther = true; + } + } + if (hasFloat && !hasInt && !hasOther) { + FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + floatKeyframes[i] = (FloatKeyframe) keyframes[i]; + } + return new FloatKeyframeSet(floatKeyframes); + } else if (hasInt && !hasFloat && !hasOther) { + IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + intKeyframes[i] = (IntKeyframe) keyframes[i]; + } + return new IntKeyframeSet(intKeyframes); + } else { + return new KeyframeSet(keyframes); + } + } + + public static KeyframeSet ofObject(Object... values) { + int numKeyframes = values.length; + ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)]; + if (numKeyframes == 1) { + keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f); + keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]); + } else { + keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]); + for (int i = 1; i < numKeyframes; ++i) { + keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]); + } + } + return new KeyframeSet(keyframes); + } + + /** + * Sets the TypeEvaluator to be used when calculating animated values. This object + * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet, + * both of which assume their own evaluator to speed up calculations with those primitive + * types. + * + * @param evaluator The TypeEvaluator to be used to calculate animated values. + */ + public void setEvaluator(TypeEvaluator evaluator) { + mEvaluator = evaluator; + } + + @Override + public KeyframeSet clone() { + ArrayList keyframes = mKeyframes; + int numKeyframes = mKeyframes.size(); + Keyframe[] newKeyframes = new Keyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = keyframes.get(i).clone(); + } + KeyframeSet newSet = new KeyframeSet(newKeyframes); + return newSet; + } + + /** + * Gets the animated value, given the elapsed fraction of the animation (interpolated by the + * animation's interpolator) and the evaluator used to calculate in-between values. This + * function maps the input fraction to the appropriate keyframe interval and a fraction + * between them and returns the interpolated value. Note that the input fraction may fall + * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a + * spring interpolation that might send the fraction past 1.0). We handle this situation by + * just using the two keyframes at the appropriate end when the value is outside those bounds. + * + * @param fraction The elapsed fraction of the animation + * @return The animated value. + */ + public Object getValue(float fraction) { + + // Special-case optimization for the common case of only two keyframes + if (mNumKeyframes == 2) { + if (mInterpolator != null) { + fraction = mInterpolator.getInterpolation(fraction); + } + return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), + mLastKeyframe.getValue()); + } + if (fraction <= 0f) { + final Keyframe nextKeyframe = mKeyframes.get(1); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + final float prevFraction = mFirstKeyframe.getFraction(); + float intervalFraction = (fraction - prevFraction) / + (nextKeyframe.getFraction() - prevFraction); + return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), + nextKeyframe.getValue()); + } else if (fraction >= 1f) { + final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2); + final /*Time*/Interpolator interpolator = mLastKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + final float prevFraction = prevKeyframe.getFraction(); + float intervalFraction = (fraction - prevFraction) / + (mLastKeyframe.getFraction() - prevFraction); + return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + mLastKeyframe.getValue()); + } + Keyframe prevKeyframe = mFirstKeyframe; + for (int i = 1; i < mNumKeyframes; ++i) { + Keyframe nextKeyframe = mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + final float prevFraction = prevKeyframe.getFraction(); + float intervalFraction = (fraction - prevFraction) / + (nextKeyframe.getFraction() - prevFraction); + return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + nextKeyframe.getValue()); + } + prevKeyframe = nextKeyframe; + } + // shouldn't reach here + return mLastKeyframe.getValue(); + } + + @Override + public String toString() { + String returnVal = " "; + for (int i = 0; i < mNumKeyframes; ++i) { + returnVal += mKeyframes.get(i).getValue() + " "; + } + return returnVal; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ObjectAnimator.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ObjectAnimator.java new file mode 100644 index 000000000..21d15c02a --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ObjectAnimator.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import android.util.Log; +//import android.util.Property; + +//import java.lang.reflect.Method; +import java.util.ArrayList; + +/** + * This subclass of {@link ValueAnimator} provides support for animating properties on target objects. + * The constructors of this class take parameters to define the target object that will be animated + * as well as the name of the property that will be animated. Appropriate set/get functions + * are then determined internally and the animation will call these functions as necessary to + * animate the property. + * + * @see #setPropertyName(String) + * + */ +@SuppressWarnings("rawtypes") +public final class ObjectAnimator extends ValueAnimator { + private static final boolean DBG = false; + + // The target object on which the property exists, set in the constructor + private Object mTarget; + + private String mPropertyName; + + //private Property mProperty; + + /** + * Sets the name of the property that will be animated. This name is used to derive + * a setter function that will be called to set animated values. + * For example, a property name of foo will result + * in a call to the function setFoo() on the target object. If either + * valueFrom or valueTo is null, then a getter function will + * also be derived and called. + * + *

For best performance of the mechanism that calls the setter function determined by the + * name of the property being animated, use float or int typed values, + * and make the setter function for those properties have a void return value. This + * will cause the code to take an optimized path for these constrained circumstances. Other + * property types and return types will work, but will have more overhead in processing + * the requests due to normal reflection mechanisms.

+ * + *

Note that the setter function derived from this property name + * must take the same parameter type as the + * valueFrom and valueTo properties, otherwise the call to + * the setter function will fail.

+ * + *

If this ObjectAnimator has been set up to animate several properties together, + * using more than one PropertyValuesHolder objects, then setting the propertyName simply + * sets the propertyName in the first of those PropertyValuesHolder objects.

+ * + * @param propertyName The name of the property being animated. Should not be null. + */ + public void setPropertyName(String propertyName) { + // mValues could be null if this is being constructed piecemeal. Just record the + // propertyName to be used later when setValues() is called if so. + if (mValues != null) { + PropertyValuesHolder valuesHolder = mValues[0]; + String oldName = valuesHolder.getPropertyName(); + valuesHolder.setPropertyName(propertyName); + mValuesMap.remove(oldName); + mValuesMap.put(propertyName, valuesHolder); + } + mPropertyName = propertyName; + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets the property that will be animated. Property objects will take precedence over + * properties specified by the {@link #setPropertyName(String)} method. Animations should + * be set up to use one or the other, not both. + * + * @param property The property being animated. Should not be null. + */ + //public void setProperty(Property property) { + // // mValues could be null if this is being constructed piecemeal. Just record the + // // propertyName to be used later when setValues() is called if so. + // if (mValues != null) { + // PropertyValuesHolder valuesHolder = mValues[0]; + // String oldName = valuesHolder.getPropertyName(); + // valuesHolder.setProperty(property); + // mValuesMap.remove(oldName); + // mValuesMap.put(mPropertyName, valuesHolder); + // } + // if (mProperty != null) { + // mPropertyName = property.getName(); + // } + // mProperty = property; + // // New property/values/target should cause re-initialization prior to starting + // mInitialized = false; + //} + + /** + * Gets the name of the property that will be animated. This name will be used to derive + * a setter function that will be called to set animated values. + * For example, a property name of foo will result + * in a call to the function setFoo() on the target object. If either + * valueFrom or valueTo is null, then a getter function will + * also be derived and called. + */ + public String getPropertyName() { + return mPropertyName; + } + + /** + * Creates a new ObjectAnimator object. This default constructor is primarily for + * use internally; the other constructors which take parameters are more generally + * useful. + */ + public ObjectAnimator() { + } + + /** + * Private utility constructor that initializes the target object and name of the + * property being animated. + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called setName(), where name is + * the value of the propertyName parameter. + * @param propertyName The name of the property being animated. + */ + private ObjectAnimator(Object target, String propertyName) { + mTarget = target; + setPropertyName(propertyName); + } + + /** + * Private utility constructor that initializes the target object and property being animated. + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + */ + //private ObjectAnimator(T target, Property property) { + // mTarget = target; + // setProperty(property); + //} + + /** + * Constructs and returns an ObjectAnimator that animates between int values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called setName(), where name is + * the value of the propertyName parameter. + * @param propertyName The name of the property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { + ObjectAnimator anim = new ObjectAnimator(target, propertyName); + anim.setIntValues(values); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between int values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + //public static ObjectAnimator ofInt(T target, Property property, int... values) { + // ObjectAnimator anim = new ObjectAnimator(target, property); + // anim.setIntValues(values); + // return anim; + //} + + /** + * Constructs and returns an ObjectAnimator that animates between float values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called setName(), where name is + * the value of the propertyName parameter. + * @param propertyName The name of the property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) { + ObjectAnimator anim = new ObjectAnimator(target, propertyName); + anim.setFloatValues(values); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between float values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + //public static ObjectAnimator ofFloat(T target, Property property, + // float... values) { + // ObjectAnimator anim = new ObjectAnimator(target, property); + // anim.setFloatValues(values); + // return anim; + //} + + /** + * Constructs and returns an ObjectAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called setName(), where name is + * the value of the propertyName parameter. + * @param propertyName The name of the property being animated. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofObject(Object target, String propertyName, + TypeEvaluator evaluator, Object... values) { + ObjectAnimator anim = new ObjectAnimator(target, propertyName); + anim.setObjectValues(values); + anim.setEvaluator(evaluator); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + //public static ObjectAnimator ofObject(T target, Property property, + // TypeEvaluator evaluator, V... values) { + // ObjectAnimator anim = new ObjectAnimator(target, property); + // anim.setObjectValues(values); + // anim.setEvaluator(evaluator); + // return anim; + //} + + /** + * Constructs and returns an ObjectAnimator that animates between the sets of values specified + * in PropertyValueHolder objects. This variant should be used when animating + * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows + * you to associate a set of animation values with a property name. + * + * @param target The object whose property is to be animated. Depending on how the + * PropertyValuesObjects were constructed, the target object should either have the {@link + * android.util.Property} objects used to construct the PropertyValuesHolder objects or (if the + * PropertyValuesHOlder objects were created with property names) the target object should have + * public methods on it called setName(), where name is the name of + * the property passed in as the propertyName parameter for each of the + * PropertyValuesHolder objects. + * @param values A set of PropertyValuesHolder objects whose values will be animated between + * over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofPropertyValuesHolder(Object target, + PropertyValuesHolder... values) { + ObjectAnimator anim = new ObjectAnimator(); + anim.mTarget = target; + anim.setValues(values); + return anim; + } + + @Override + public void setIntValues(int... values) { + if (mValues == null || mValues.length == 0) { + // No values yet - this animator is being constructed piecemeal. Init the values with + // whatever the current propertyName is + //if (mProperty != null) { + // setValues(PropertyValuesHolder.ofInt(mProperty, values)); + //} else { + setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); + //} + } else { + super.setIntValues(values); + } + } + + @Override + public void setFloatValues(float... values) { + if (mValues == null || mValues.length == 0) { + // No values yet - this animator is being constructed piecemeal. Init the values with + // whatever the current propertyName is + //if (mProperty != null) { + // setValues(PropertyValuesHolder.ofFloat(mProperty, values)); + //} else { + setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)); + //} + } else { + super.setFloatValues(values); + } + } + + @Override + public void setObjectValues(Object... values) { + if (mValues == null || mValues.length == 0) { + // No values yet - this animator is being constructed piecemeal. Init the values with + // whatever the current propertyName is + //if (mProperty != null) { + // setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values)); + //} else { + setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values)); + //} + } else { + super.setObjectValues(values); + } + } + + @Override + public void start() { + if (DBG) { + Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration()); + for (int i = 0; i < mValues.length; ++i) { + PropertyValuesHolder pvh = mValues[i]; + ArrayList keyframes = pvh.mKeyframeSet.mKeyframes; + Log.d("ObjectAnimator", " Values[" + i + "]: " + + pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " + + keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue()); + } + } + super.start(); + } + + /** + * This function is called immediately before processing the first animation + * frame of an animation. If there is a nonzero startDelay, the + * function is called after that delay ends. + * It takes care of the final initialization steps for the + * animation. This includes setting mEvaluator, if the user has not yet + * set it up, and the setter/getter methods, if the user did not supply + * them. + * + *

Overriders of this method should call the superclass method to cause + * internal mechanisms to be set up correctly.

+ */ + @Override + void initAnimation() { + if (!mInitialized) { + // mValueType may change due to setter/getter setup; do this before calling super.init(), + // which uses mValueType to set up the default type evaluator. + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupSetterAndGetter(mTarget); + } + super.initAnimation(); + } + } + + /** + * Sets the length of the animation. The default duration is 300 milliseconds. + * + * @param duration The length of the animation, in milliseconds. + * @return ObjectAnimator The object called with setDuration(). This return + * value makes it easier to compose statements together that construct and then set the + * duration, as in + * ObjectAnimator.ofInt(target, propertyName, 0, 10).setDuration(500).start(). + */ + @Override + public ObjectAnimator setDuration(long duration) { + super.setDuration(duration); + return this; + } + + + /** + * The target object whose property will be animated by this animation + * + * @return The object being animated + */ + public Object getTarget() { + return mTarget; + } + + /** + * Sets the target object whose property will be animated by this animation + * + * @param target The object being animated + */ + @Override + public void setTarget(Object target) { + if (mTarget != target) { + final Object oldTarget = mTarget; + mTarget = target; + if (oldTarget != null && target != null && oldTarget.getClass() == target.getClass()) { + return; + } + // New target type should cause re-initialization prior to starting + mInitialized = false; + } + } + + @Override + public void setupStartValues() { + initAnimation(); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupStartValue(mTarget); + } + } + + @Override + public void setupEndValues() { + initAnimation(); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupEndValue(mTarget); + } + } + + /** + * This method is called with the elapsed fraction of the animation during every + * animation frame. This function turns the elapsed fraction into an interpolated fraction + * and then into an animated value (from the evaluator. The function is called mostly during + * animation updates, but it is also called when the end() + * function is called, to set the final value on the property. + * + *

Overrides of this method must call the superclass to perform the calculation + * of the animated value.

+ * + * @param fraction The elapsed fraction of the animation. + */ + @Override + void animateValue(float fraction) { + super.animateValue(fraction); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setAnimatedValue(mTarget); + } + } + + @Override + public ObjectAnimator clone() { + final ObjectAnimator anim = (ObjectAnimator) super.clone(); + return anim; + } + + @Override + public String toString() { + String returnVal = "ObjectAnimator@" + Integer.toHexString(hashCode()) + ", target " + + mTarget; + if (mValues != null) { + for (int i = 0; i < mValues.length; ++i) { + returnVal += "\n " + mValues[i].toString(); + } + } + return returnVal; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/PropertyValuesHolder.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/PropertyValuesHolder.java new file mode 100644 index 000000000..3a5659506 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/PropertyValuesHolder.java @@ -0,0 +1,1012 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +//import android.util.FloatProperty; +//import android.util.IntProperty; +import android.util.Log; +//import android.util.Property; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * This class holds information about a property and the values that that property + * should take on during an animation. PropertyValuesHolder objects can be used to create + * animations with ValueAnimator or ObjectAnimator that operate on several different properties + * in parallel. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class PropertyValuesHolder implements Cloneable { + + /** + * The name of the property associated with the values. This need not be a real property, + * unless this object is being used with ObjectAnimator. But this is the name by which + * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator. + */ + String mPropertyName; + + /** + * @hide + */ + //protected Property mProperty; + + /** + * The setter function, if needed. ObjectAnimator hands off this functionality to + * PropertyValuesHolder, since it holds all of the per-property information. This + * property is automatically + * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. + */ + Method mSetter = null; + + /** + * The getter function, if needed. ObjectAnimator hands off this functionality to + * PropertyValuesHolder, since it holds all of the per-property information. This + * property is automatically + * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. + * The getter is only derived and used if one of the values is null. + */ + private Method mGetter = null; + + /** + * The type of values supplied. This information is used both in deriving the setter/getter + * functions and in deriving the type of TypeEvaluator. + */ + Class mValueType; + + /** + * The set of keyframes (time/value pairs) that define this animation. + */ + KeyframeSet mKeyframeSet = null; + + + // type evaluators for the primitive types handled by this implementation + private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); + private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); + + // We try several different types when searching for appropriate setter/getter functions. + // The caller may have supplied values in a type that does not match the setter/getter + // functions (such as the integers 0 and 1 to represent floating point values for alpha). + // Also, the use of generics in constructors means that we end up with the Object versions + // of primitive types (Float vs. float). But most likely, the setter/getter functions + // will take primitive types instead. + // So we supply an ordered array of other types to try before giving up. + private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, + Double.class, Integer.class}; + private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, + Float.class, Double.class}; + private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class, + Float.class, Integer.class}; + + // These maps hold all property entries for a particular class. This map + // is used to speed up property/setter/getter lookups for a given class/property + // combination. No need to use reflection on the combination more than once. + private static final HashMap> sSetterPropertyMap = + new HashMap>(); + private static final HashMap> sGetterPropertyMap = + new HashMap>(); + + // This lock is used to ensure that only one thread is accessing the property maps + // at a time. + final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock(); + + // Used to pass single value to varargs parameter in setter invocation + final Object[] mTmpValueArray = new Object[1]; + + /** + * The type evaluator used to calculate the animated values. This evaluator is determined + * automatically based on the type of the start/end objects passed into the constructor, + * but the system only knows about the primitive types int and float. Any other + * type will need to set the evaluator to a custom evaluator for that type. + */ + private TypeEvaluator mEvaluator; + + /** + * The value most recently calculated by calculateValue(). This is set during + * that function and might be retrieved later either by ValueAnimator.animatedValue() or + * by the property-setting logic in ObjectAnimator.animatedValue(). + */ + private Object mAnimatedValue; + + /** + * Internal utility constructor, used by the factory methods to set the property name. + * @param propertyName The name of the property for this holder. + */ + private PropertyValuesHolder(String propertyName) { + mPropertyName = propertyName; + } + + /** + * Internal utility constructor, used by the factory methods to set the property. + * @param property The property for this holder. + */ + //private PropertyValuesHolder(Property property) { + // mProperty = property; + // if (property != null) { + // mPropertyName = property.getName(); + // } + //} + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * set of int values. + * @param propertyName The name of the property being animated. + * @param values The values that the named property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofInt(String propertyName, int... values) { + return new IntPropertyValuesHolder(propertyName, values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of int values. + * @param property The property being animated. Should not be null. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + //public static PropertyValuesHolder ofInt(Property property, int... values) { + // return new IntPropertyValuesHolder(property, values); + //} + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * set of float values. + * @param propertyName The name of the property being animated. + * @param values The values that the named property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofFloat(String propertyName, float... values) { + return new FloatPropertyValuesHolder(propertyName, values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of float values. + * @param property The property being animated. Should not be null. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + //public static PropertyValuesHolder ofFloat(Property property, float... values) { + // return new FloatPropertyValuesHolder(property, values); + //} + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * set of Object values. This variant also takes a TypeEvaluator because the system + * cannot automatically interpolate between objects of unknown type. + * + * @param propertyName The name of the property being animated. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the named property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, + Object... values) { + PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); + pvh.setObjectValues(values); + pvh.setEvaluator(evaluator); + return pvh; + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of Object values. This variant also takes a TypeEvaluator because the system + * cannot automatically interpolate between objects of unknown type. + * + * @param property The property being animated. Should not be null. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + //public static PropertyValuesHolder ofObject(Property property, + // TypeEvaluator evaluator, V... values) { + // PropertyValuesHolder pvh = new PropertyValuesHolder(property); + // pvh.setObjectValues(values); + // pvh.setEvaluator(evaluator); + // return pvh; + //} + + /** + * Constructs and returns a PropertyValuesHolder object with the specified property name and set + * of values. These values can be of any type, but the type should be consistent so that + * an appropriate {@link android.animation.TypeEvaluator} can be found that matches + * the common type. + *

If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from propertyName, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * @param propertyName The name of the property associated with this set of values. This + * can be the actual property name to be used when using a ObjectAnimator object, or + * just a name used to get animated values, such as if this object is used with an + * ValueAnimator object. + * @param values The set of values to animate between. + */ + public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) { + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); + if (keyframeSet instanceof IntKeyframeSet) { + return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet); + } else if (keyframeSet instanceof FloatKeyframeSet) { + return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet); + } + else { + PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); + pvh.mKeyframeSet = keyframeSet; + pvh.mValueType = ((Keyframe)values[0]).getType(); + return pvh; + } + } + + /** + * Constructs and returns a PropertyValuesHolder object with the specified property and set + * of values. These values can be of any type, but the type should be consistent so that + * an appropriate {@link android.animation.TypeEvaluator} can be found that matches + * the common type. + *

If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling the property's + * {@link android.util.Property#get(Object)} function. + * Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction with + * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * @param property The property associated with this set of values. Should not be null. + * @param values The set of values to animate between. + */ + //public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) { + // KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); + // if (keyframeSet instanceof IntKeyframeSet) { + // return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet); + // } else if (keyframeSet instanceof FloatKeyframeSet) { + // return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet); + // } + // else { + // PropertyValuesHolder pvh = new PropertyValuesHolder(property); + // pvh.mKeyframeSet = keyframeSet; + // pvh.mValueType = ((Keyframe)values[0]).getType(); + // return pvh; + // } + //} + + /** + * Set the animated values for this object to this set of ints. + * If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from propertyName, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * + * @param values One or more values that the animation will animate between. + */ + public void setIntValues(int... values) { + mValueType = int.class; + mKeyframeSet = KeyframeSet.ofInt(values); + } + + /** + * Set the animated values for this object to this set of floats. + * If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from propertyName, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * + * @param values One or more values that the animation will animate between. + */ + public void setFloatValues(float... values) { + mValueType = float.class; + mKeyframeSet = KeyframeSet.ofFloat(values); + } + + /** + * Set the animated values for this object to this set of Keyframes. + * + * @param values One or more values that the animation will animate between. + */ + public void setKeyframes(Keyframe... values) { + int numKeyframes = values.length; + Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)]; + mValueType = ((Keyframe)values[0]).getType(); + for (int i = 0; i < numKeyframes; ++i) { + keyframes[i] = (Keyframe)values[i]; + } + mKeyframeSet = new KeyframeSet(keyframes); + } + + /** + * Set the animated values for this object to this set of Objects. + * If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from propertyName, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * + * @param values One or more values that the animation will animate between. + */ + public void setObjectValues(Object... values) { + mValueType = values[0].getClass(); + mKeyframeSet = KeyframeSet.ofObject(values); + } + + /** + * Determine the setter or getter function using the JavaBeans convention of setFoo or + * getFoo for a property named 'foo'. This function figures out what the name of the + * function should be and uses reflection to find the Method with that name on the + * target object. + * + * @param targetClass The class to search for the method + * @param prefix "set" or "get", depending on whether we need a setter or getter. + * @param valueType The type of the parameter (in the case of a setter). This type + * is derived from the values set on this PropertyValuesHolder. This type is used as + * a first guess at the parameter type, but we check for methods with several different + * types to avoid problems with slight mis-matches between supplied values and actual + * value types used on the setter. + * @return Method the method associated with mPropertyName. + */ + private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) { + // TODO: faster implementation... + Method returnVal = null; + String methodName = getMethodName(prefix, mPropertyName); + Class args[] = null; + if (valueType == null) { + try { + returnVal = targetClass.getMethod(methodName, args); + } catch (NoSuchMethodException e) { + Log.e("PropertyValuesHolder", targetClass.getSimpleName() + " - " + + "Couldn't find no-arg method for property " + mPropertyName + ": " + e); + } + } else { + args = new Class[1]; + Class typeVariants[]; + if (mValueType.equals(Float.class)) { + typeVariants = FLOAT_VARIANTS; + } else if (mValueType.equals(Integer.class)) { + typeVariants = INTEGER_VARIANTS; + } else if (mValueType.equals(Double.class)) { + typeVariants = DOUBLE_VARIANTS; + } else { + typeVariants = new Class[1]; + typeVariants[0] = mValueType; + } + for (Class typeVariant : typeVariants) { + args[0] = typeVariant; + try { + returnVal = targetClass.getMethod(methodName, args); + // change the value type to suit + mValueType = typeVariant; + return returnVal; + } catch (NoSuchMethodException e) { + // Swallow the error and keep trying other variants + } + } + // If we got here, then no appropriate function was found + Log.e("PropertyValuesHolder", + "Couldn't find " + prefix + "ter property " + mPropertyName + + " for " + targetClass.getSimpleName() + + " with value type "+ mValueType); + } + + return returnVal; + } + + + /** + * Returns the setter or getter requested. This utility function checks whether the + * requested method exists in the propertyMapMap cache. If not, it calls another + * utility function to request the Method from the targetClass directly. + * @param targetClass The Class on which the requested method should exist. + * @param propertyMapMap The cache of setters/getters derived so far. + * @param prefix "set" or "get", for the setter or getter. + * @param valueType The type of parameter passed into the method (null for getter). + * @return Method the method associated with mPropertyName. + */ + private Method setupSetterOrGetter(Class targetClass, + HashMap> propertyMapMap, + String prefix, Class valueType) { + Method setterOrGetter = null; + try { + // Have to lock property map prior to reading it, to guard against + // another thread putting something in there after we've checked it + // but before we've added an entry to it + mPropertyMapLock.writeLock().lock(); + HashMap propertyMap = propertyMapMap.get(targetClass); + if (propertyMap != null) { + setterOrGetter = propertyMap.get(mPropertyName); + } + if (setterOrGetter == null) { + setterOrGetter = getPropertyFunction(targetClass, prefix, valueType); + if (propertyMap == null) { + propertyMap = new HashMap(); + propertyMapMap.put(targetClass, propertyMap); + } + propertyMap.put(mPropertyName, setterOrGetter); + } + } finally { + mPropertyMapLock.writeLock().unlock(); + } + return setterOrGetter; + } + + /** + * Utility function to get the setter from targetClass + * @param targetClass The Class on which the requested method should exist. + */ + void setupSetter(Class targetClass) { + mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType); + } + + /** + * Utility function to get the getter from targetClass + */ + private void setupGetter(Class targetClass) { + mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null); + } + + /** + * Internal function (called from ObjectAnimator) to set up the setter and getter + * prior to running the animation. If the setter has not been manually set for this + * object, it will be derived automatically given the property name, target object, and + * types of values supplied. If no getter has been set, it will be supplied iff any of the + * supplied values was null. If there is a null value, then the getter (supplied or derived) + * will be called to set those null values to the current value of the property + * on the target object. + * @param target The object on which the setter (and possibly getter) exist. + */ + void setupSetterAndGetter(Object target) { + //if (mProperty != null) { + // // check to make sure that mProperty is on the class of target + // try { + // Object testValue = mProperty.get(target); + // for (Keyframe kf : mKeyframeSet.mKeyframes) { + // if (!kf.hasValue()) { + // kf.setValue(mProperty.get(target)); + // } + // } + // return; + // } catch (ClassCastException e) { + // Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() + + // ") on target object " + target + ". Trying reflection instead"); + // mProperty = null; + // } + //} + Class targetClass = target.getClass(); + if (mSetter == null) { + setupSetter(targetClass); + } + for (Keyframe kf : mKeyframeSet.mKeyframes) { + if (!kf.hasValue()) { + if (mGetter == null) { + setupGetter(targetClass); + } + try { + kf.setValue(mGetter.invoke(target)); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + } + + /** + * Utility function to set the value stored in a particular Keyframe. The value used is + * whatever the value is for the property name specified in the keyframe on the target object. + * + * @param target The target object from which the current value should be extracted. + * @param kf The keyframe which holds the property name and value. + */ + private void setupValue(Object target, Keyframe kf) { + //if (mProperty != null) { + // kf.setValue(mProperty.get(target)); + //} + try { + if (mGetter == null) { + Class targetClass = target.getClass(); + setupGetter(targetClass); + } + kf.setValue(mGetter.invoke(target)); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + + /** + * This function is called by ObjectAnimator when setting the start values for an animation. + * The start values are set according to the current values in the target object. The + * property whose value is extracted is whatever is specified by the propertyName of this + * PropertyValuesHolder object. + * + * @param target The object which holds the start values that should be set. + */ + void setupStartValue(Object target) { + setupValue(target, mKeyframeSet.mKeyframes.get(0)); + } + + /** + * This function is called by ObjectAnimator when setting the end values for an animation. + * The end values are set according to the current values in the target object. The + * property whose value is extracted is whatever is specified by the propertyName of this + * PropertyValuesHolder object. + * + * @param target The object which holds the start values that should be set. + */ + void setupEndValue(Object target) { + setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1)); + } + + @Override + public PropertyValuesHolder clone() { + try { + PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone(); + newPVH.mPropertyName = mPropertyName; + //newPVH.mProperty = mProperty; + newPVH.mKeyframeSet = mKeyframeSet.clone(); + newPVH.mEvaluator = mEvaluator; + return newPVH; + } catch (CloneNotSupportedException e) { + // won't reach here + return null; + } + } + + /** + * Internal function to set the value on the target object, using the setter set up + * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator + * to handle turning the value calculated by ValueAnimator into a value set on the object + * according to the name of the property. + * @param target The target object on which the value is set + */ + void setAnimatedValue(Object target) { + //if (mProperty != null) { + // mProperty.set(target, getAnimatedValue()); + //} + if (mSetter != null) { + try { + mTmpValueArray[0] = getAnimatedValue(); + mSetter.invoke(target, mTmpValueArray); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + + /** + * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used + * to calculate animated values. + */ + void init() { + if (mEvaluator == null) { + // We already handle int and float automatically, but not their Object + // equivalents + mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : + (mValueType == Float.class) ? sFloatEvaluator : + null; + } + if (mEvaluator != null) { + // KeyframeSet knows how to evaluate the common types - only give it a custom + // evaluator if one has been set on this class + mKeyframeSet.setEvaluator(mEvaluator); + } + } + + /** + * The TypeEvaluator will the automatically determined based on the type of values + * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so + * desired. This may be important in cases where either the type of the values supplied + * do not match the way that they should be interpolated between, or if the values + * are of a custom type or one not currently understood by the animation system. Currently, + * only values of type float and int (and their Object equivalents: Float + * and Integer) are correctly interpolated; all other types require setting a TypeEvaluator. + * @param evaluator + */ + public void setEvaluator(TypeEvaluator evaluator) { + mEvaluator = evaluator; + mKeyframeSet.setEvaluator(evaluator); + } + + /** + * Function used to calculate the value according to the evaluator set up for + * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue(). + * + * @param fraction The elapsed, interpolated fraction of the animation. + */ + void calculateValue(float fraction) { + mAnimatedValue = mKeyframeSet.getValue(fraction); + } + + /** + * Sets the name of the property that will be animated. This name is used to derive + * a setter function that will be called to set animated values. + * For example, a property name of foo will result + * in a call to the function setFoo() on the target object. If either + * valueFrom or valueTo is null, then a getter function will + * also be derived and called. + * + *

Note that the setter function derived from this property name + * must take the same parameter type as the + * valueFrom and valueTo properties, otherwise the call to + * the setter function will fail.

+ * + * @param propertyName The name of the property being animated. + */ + public void setPropertyName(String propertyName) { + mPropertyName = propertyName; + } + + /** + * Sets the property that will be animated. + * + *

Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property + * must exist on the target object specified in that ObjectAnimator.

+ * + * @param property The property being animated. + */ + //public void setProperty(Property property) { + // mProperty = property; + //} + + /** + * Gets the name of the property that will be animated. This name will be used to derive + * a setter function that will be called to set animated values. + * For example, a property name of foo will result + * in a call to the function setFoo() on the target object. If either + * valueFrom or valueTo is null, then a getter function will + * also be derived and called. + */ + public String getPropertyName() { + return mPropertyName; + } + + /** + * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value + * most recently calculated in calculateValue(). + * @return + */ + Object getAnimatedValue() { + return mAnimatedValue; + } + + @Override + public String toString() { + return mPropertyName + ": " + mKeyframeSet.toString(); + } + + /** + * Utility method to derive a setter/getter method name from a property name, where the + * prefix is typically "set" or "get" and the first letter of the property name is + * capitalized. + * + * @param prefix The precursor to the method name, before the property name begins, typically + * "set" or "get". + * @param propertyName The name of the property that represents the bulk of the method name + * after the prefix. The first letter of this word will be capitalized in the resulting + * method name. + * @return String the property name converted to a method name according to the conventions + * specified above. + */ + static String getMethodName(String prefix, String propertyName) { + if (propertyName == null || propertyName.length() == 0) { + // shouldn't get here + return prefix; + } + char firstLetter = Character.toUpperCase(propertyName.charAt(0)); + String theRest = propertyName.substring(1); + return prefix + firstLetter + theRest; + } + + static class IntPropertyValuesHolder extends PropertyValuesHolder { + + // Cache JNI functions to avoid looking them up twice + //private static final HashMap> sJNISetterPropertyMap = + // new HashMap>(); + //int mJniSetter; + //private IntProperty mIntProperty; + + IntKeyframeSet mIntKeyframeSet; + int mIntAnimatedValue; + + public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) { + super(propertyName); + mValueType = int.class; + mKeyframeSet = keyframeSet; + mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + } + + //public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) { + // super(property); + // mValueType = int.class; + // mKeyframeSet = keyframeSet; + // mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + // if (property instanceof IntProperty) { + // mIntProperty = (IntProperty) mProperty; + // } + //} + + public IntPropertyValuesHolder(String propertyName, int... values) { + super(propertyName); + setIntValues(values); + } + + //public IntPropertyValuesHolder(Property property, int... values) { + // super(property); + // setIntValues(values); + // if (property instanceof IntProperty) { + // mIntProperty = (IntProperty) mProperty; + // } + //} + + @Override + public void setIntValues(int... values) { + super.setIntValues(values); + mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + } + + @Override + void calculateValue(float fraction) { + mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction); + } + + @Override + Object getAnimatedValue() { + return mIntAnimatedValue; + } + + @Override + public IntPropertyValuesHolder clone() { + IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone(); + newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet; + return newPVH; + } + + /** + * Internal function to set the value on the target object, using the setter set up + * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator + * to handle turning the value calculated by ValueAnimator into a value set on the object + * according to the name of the property. + * @param target The target object on which the value is set + */ + @Override + void setAnimatedValue(Object target) { + //if (mIntProperty != null) { + // mIntProperty.setValue(target, mIntAnimatedValue); + // return; + //} + //if (mProperty != null) { + // mProperty.set(target, mIntAnimatedValue); + // return; + //} + //if (mJniSetter != 0) { + // nCallIntMethod(target, mJniSetter, mIntAnimatedValue); + // return; + //} + if (mSetter != null) { + try { + mTmpValueArray[0] = mIntAnimatedValue; + mSetter.invoke(target, mTmpValueArray); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + + @Override + void setupSetter(Class targetClass) { + //if (mProperty != null) { + // return; + //} + // Check new static hashmap for setter method + //try { + // mPropertyMapLock.writeLock().lock(); + // HashMap propertyMap = sJNISetterPropertyMap.get(targetClass); + // if (propertyMap != null) { + // Integer mJniSetterInteger = propertyMap.get(mPropertyName); + // if (mJniSetterInteger != null) { + // mJniSetter = mJniSetterInteger; + // } + // } + // if (mJniSetter == 0) { + // String methodName = getMethodName("set", mPropertyName); + // mJniSetter = nGetIntMethod(targetClass, methodName); + // if (mJniSetter != 0) { + // if (propertyMap == null) { + // propertyMap = new HashMap(); + // sJNISetterPropertyMap.put(targetClass, propertyMap); + // } + // propertyMap.put(mPropertyName, mJniSetter); + // } + // } + //} catch (NoSuchMethodError e) { + // Log.d("PropertyValuesHolder", + // "Can't find native method using JNI, use reflection" + e); + //} finally { + // mPropertyMapLock.writeLock().unlock(); + //} + //if (mJniSetter == 0) { + // Couldn't find method through fast JNI approach - just use reflection + super.setupSetter(targetClass); + //} + } + } + + static class FloatPropertyValuesHolder extends PropertyValuesHolder { + + // Cache JNI functions to avoid looking them up twice + //private static final HashMap> sJNISetterPropertyMap = + // new HashMap>(); + //int mJniSetter; + //private FloatProperty mFloatProperty; + + FloatKeyframeSet mFloatKeyframeSet; + float mFloatAnimatedValue; + + public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) { + super(propertyName); + mValueType = float.class; + mKeyframeSet = keyframeSet; + mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + } + + //public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) { + // super(property); + // mValueType = float.class; + // mKeyframeSet = keyframeSet; + // mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + // if (property instanceof FloatProperty) { + // mFloatProperty = (FloatProperty) mProperty; + // } + //} + + public FloatPropertyValuesHolder(String propertyName, float... values) { + super(propertyName); + setFloatValues(values); + } + + //public FloatPropertyValuesHolder(Property property, float... values) { + // super(property); + // setFloatValues(values); + // if (property instanceof FloatProperty) { + // mFloatProperty = (FloatProperty) mProperty; + // } + //} + + @Override + public void setFloatValues(float... values) { + super.setFloatValues(values); + mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + } + + @Override + void calculateValue(float fraction) { + mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction); + } + + @Override + Object getAnimatedValue() { + return mFloatAnimatedValue; + } + + @Override + public FloatPropertyValuesHolder clone() { + FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone(); + newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet; + return newPVH; + } + + /** + * Internal function to set the value on the target object, using the setter set up + * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator + * to handle turning the value calculated by ValueAnimator into a value set on the object + * according to the name of the property. + * @param target The target object on which the value is set + */ + @Override + void setAnimatedValue(Object target) { + //if (mFloatProperty != null) { + // mFloatProperty.setValue(target, mFloatAnimatedValue); + // return; + //} + //if (mProperty != null) { + // mProperty.set(target, mFloatAnimatedValue); + // return; + //} + //if (mJniSetter != 0) { + // nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue); + // return; + //} + if (mSetter != null) { + try { + mTmpValueArray[0] = mFloatAnimatedValue; + mSetter.invoke(target, mTmpValueArray); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + + @Override + void setupSetter(Class targetClass) { + //if (mProperty != null) { + // return; + //} + // Check new static hashmap for setter method + //try { + // mPropertyMapLock.writeLock().lock(); + // HashMap propertyMap = sJNISetterPropertyMap.get(targetClass); + // if (propertyMap != null) { + // Integer mJniSetterInteger = propertyMap.get(mPropertyName); + // if (mJniSetterInteger != null) { + // mJniSetter = mJniSetterInteger; + // } + // } + // if (mJniSetter == 0) { + // String methodName = getMethodName("set", mPropertyName); + // mJniSetter = nGetFloatMethod(targetClass, methodName); + // if (mJniSetter != 0) { + // if (propertyMap == null) { + // propertyMap = new HashMap(); + // sJNISetterPropertyMap.put(targetClass, propertyMap); + // } + // propertyMap.put(mPropertyName, mJniSetter); + // } + // } + //} catch (NoSuchMethodError e) { + // Log.d("PropertyValuesHolder", + // "Can't find native method using JNI, use reflection" + e); + //} finally { + // mPropertyMapLock.writeLock().unlock(); + //} + //if (mJniSetter == 0) { + // Couldn't find method through fast JNI approach - just use reflection + super.setupSetter(targetClass); + //} + } + + } + + //native static private int nGetIntMethod(Class targetClass, String methodName); + //native static private int nGetFloatMethod(Class targetClass, String methodName); + //native static private void nCallIntMethod(Object target, int methodID, int arg); + //native static private void nCallFloatMethod(Object target, int methodID, float arg); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/TypeEvaluator.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/TypeEvaluator.java new file mode 100644 index 000000000..0ea319244 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/TypeEvaluator.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +/** + * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators + * allow developers to create animations on arbitrary property types, by allowing them to supply + * custom evaulators for types that are not automatically understood and used by the animation + * system. + * + * @see ValueAnimator#setEvaluator(TypeEvaluator) + */ +public interface TypeEvaluator { + + /** + * This function returns the result of linearly interpolating the start and end values, with + * fraction representing the proportion between the start and end values. The + * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0), + * where x0 is startValue, x1 is endValue, + * and t is fraction. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value. + * @param endValue The end value. + * @return A linear interpolation between the start and end values, given the + * fraction parameter. + */ + public T evaluate(float fraction, T startValue, T endValue); + +} \ No newline at end of file diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ValueAnimator.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ValueAnimator.java new file mode 100644 index 000000000..333d49e18 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/animation/ValueAnimator.java @@ -0,0 +1,1265 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.nineoldandroids.animation; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AndroidRuntimeException; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This class provides a simple timing engine for running animations + * which calculate animated values and set them on target objects. + * + *

There is a single timing pulse that all animations use. It runs in a + * custom handler to ensure that property changes happen on the UI thread.

+ * + *

By default, ValueAnimator uses non-linear time interpolation, via the + * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates + * out of an animation. This behavior can be changed by calling + * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.

+ */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ValueAnimator extends Animator { + + /** + * Internal constants + */ + + /* + * The default amount of time in ms between animation frames + */ + private static final long DEFAULT_FRAME_DELAY = 10; + + /** + * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent + * by the handler to itself to process the next animation frame + */ + static final int ANIMATION_START = 0; + static final int ANIMATION_FRAME = 1; + + /** + * Values used with internal variable mPlayingState to indicate the current state of an + * animation. + */ + static final int STOPPED = 0; // Not yet playing + static final int RUNNING = 1; // Playing normally + static final int SEEKED = 2; // Seeked to some time value + + /** + * Internal variables + * NOTE: This object implements the clone() method, making a deep copy of any referenced + * objects. As other non-trivial fields are added to this class, make sure to add logic + * to clone() to make deep copies of them. + */ + + // The first time that the animation's animateFrame() method is called. This time is used to + // determine elapsed time (and therefore the elapsed fraction) in subsequent calls + // to animateFrame() + long mStartTime; + + /** + * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked + * to a value. + */ + long mSeekTime = -1; + + // TODO: We access the following ThreadLocal variables often, some of them on every update. + // If ThreadLocal access is significantly expensive, we may want to put all of these + // fields into a structure sot hat we just access ThreadLocal once to get the reference + // to that structure, then access the structure directly for each field. + + // The static sAnimationHandler processes the internal timing loop on which all animations + // are based + private static ThreadLocal sAnimationHandler = + new ThreadLocal(); + + // The per-thread list of all active animations + private static final ThreadLocal> sAnimations = + new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + // The per-thread set of animations to be started on the next animation frame + private static final ThreadLocal> sPendingAnimations = + new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + /** + * Internal per-thread collections used to avoid set collisions as animations start and end + * while being processed. + */ + private static final ThreadLocal> sDelayedAnims = + new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + private static final ThreadLocal> sEndingAnims = + new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + private static final ThreadLocal> sReadyAnims = + new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + // The time interpolator to be used if none is set on the animation + private static final /*Time*/Interpolator sDefaultInterpolator = + new AccelerateDecelerateInterpolator(); + + // type evaluators for the primitive types handled by this implementation + //private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); + //private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); + + /** + * Used to indicate whether the animation is currently playing in reverse. This causes the + * elapsed fraction to be inverted to calculate the appropriate values. + */ + private boolean mPlayingBackwards = false; + + /** + * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the + * repeatCount (if repeatCount!=INFINITE), the animation ends + */ + private int mCurrentIteration = 0; + + /** + * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction(). + */ + private float mCurrentFraction = 0f; + + /** + * Tracks whether a startDelay'd animation has begun playing through the startDelay. + */ + private boolean mStartedDelay = false; + + /** + * Tracks the time at which the animation began playing through its startDelay. This is + * different from the mStartTime variable, which is used to track when the animation became + * active (which is when the startDelay expired and the animation was added to the active + * animations list). + */ + private long mDelayStartTime; + + /** + * Flag that represents the current state of the animation. Used to figure out when to start + * an animation (if state == STOPPED). Also used to end an animation that + * has been cancel()'d or end()'d since the last animation frame. Possible values are + * STOPPED, RUNNING, SEEKED. + */ + int mPlayingState = STOPPED; + + /** + * Additional playing state to indicate whether an animator has been start()'d. There is + * some lag between a call to start() and the first animation frame. We should still note + * that the animation has been started, even if it's first animation frame has not yet + * happened, and reflect that state in isRunning(). + * Note that delayed animations are different: they are not started until their first + * animation frame, which occurs after their delay elapses. + */ + private boolean mRunning = false; + + /** + * Additional playing state to indicate whether an animator has been start()'d, whether or + * not there is a nonzero startDelay. + */ + private boolean mStarted = false; + + /** + * Flag that denotes whether the animation is set up and ready to go. Used to + * set up animation that has not yet been started. + */ + boolean mInitialized = false; + + // + // Backing variables + // + + // How long the animation should last in ms + private long mDuration = 300; + + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + // The number of milliseconds between animation frames + private static long sFrameDelay = DEFAULT_FRAME_DELAY; + + // The number of times the animation will repeat. The default is 0, which means the animation + // will play only once + private int mRepeatCount = 0; + + /** + * The type of repetition that will occur when repeatMode is nonzero. RESTART means the + * animation will start from the beginning on every new cycle. REVERSE means the animation + * will reverse directions on each iteration. + */ + private int mRepeatMode = RESTART; + + /** + * The time interpolator to be used. The elapsed fraction of the animation will be passed + * through this interpolator to calculate the interpolated fraction, which is then used to + * calculate the animated values. + */ + private /*Time*/Interpolator mInterpolator = sDefaultInterpolator; + + /** + * The set of listeners to be sent events through the life of an animation. + */ + private ArrayList mUpdateListeners = null; + + /** + * The property/value sets being animated. + */ + PropertyValuesHolder[] mValues; + + /** + * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values + * by property name during calls to getAnimatedValue(String). + */ + HashMap mValuesMap; + + /** + * Public constants + */ + + /** + * When the animation reaches the end and repeatCount is INFINITE + * or a positive value, the animation restarts from the beginning. + */ + public static final int RESTART = 1; + /** + * When the animation reaches the end and repeatCount is INFINITE + * or a positive value, the animation reverses direction on every iteration. + */ + public static final int REVERSE = 2; + /** + * This value used used with the {@link #setRepeatCount(int)} property to repeat + * the animation indefinitely. + */ + public static final int INFINITE = -1; + + /** + * Creates a new ValueAnimator object. This default constructor is primarily for + * use internally; the factory methods which take parameters are more generally + * useful. + */ + public ValueAnimator() { + } + + /** + * Constructs and returns a ValueAnimator that animates between int values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofInt(int... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setIntValues(values); + return anim; + } + + /** + * Constructs and returns a ValueAnimator that animates between float values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofFloat(float... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setFloatValues(values); + return anim; + } + + /** + * Constructs and returns a ValueAnimator that animates between the values + * specified in the PropertyValuesHolder objects. + * + * @param values A set of PropertyValuesHolder objects whose values will be animated + * between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setValues(values); + return anim; + } + /** + * Constructs and returns a ValueAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + *

Since ValueAnimator does not know how to animate between arbitrary Objects, this + * factory method also takes a TypeEvaluator object that the ValueAnimator will use + * to perform that interpolation. + * + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the ncessry interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setObjectValues(values); + anim.setEvaluator(evaluator); + return anim; + } + + /** + * Sets int values that will be animated between. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + *

If there are already multiple sets of values defined for this ValueAnimator via more + * than one PropertyValuesHolder object, this method will set the values for the first + * of those objects.

+ * + * @param values A set of values that the animation will animate between over time. + */ + public void setIntValues(int... values) { + if (values == null || values.length == 0) { + return; + } + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofInt("", values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setIntValues(values); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets float values that will be animated between. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + *

If there are already multiple sets of values defined for this ValueAnimator via more + * than one PropertyValuesHolder object, this method will set the values for the first + * of those objects.

+ * + * @param values A set of values that the animation will animate between over time. + */ + public void setFloatValues(float... values) { + if (values == null || values.length == 0) { + return; + } + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofFloat("", values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setFloatValues(values); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets the values to animate between for this animation. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + *

If there are already multiple sets of values defined for this ValueAnimator via more + * than one PropertyValuesHolder object, this method will set the values for the first + * of those objects.

+ * + *

There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate + * between these value objects. ValueAnimator only knows how to interpolate between the + * primitive types specified in the other setValues() methods.

+ * + * @param values The set of values to animate between. + */ + public void setObjectValues(Object... values) { + if (values == null || values.length == 0) { + return; + } + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofObject("", + (TypeEvaluator)null, values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setObjectValues(values); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets the values, per property, being animated between. This function is called internally + * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can + * be constructed without values and this method can be called to set the values manually + * instead. + * + * @param values The set of values, per property, being animated between. + */ + public void setValues(PropertyValuesHolder... values) { + int numValues = values.length; + mValues = values; + mValuesMap = new HashMap(numValues); + for (int i = 0; i < numValues; ++i) { + PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i]; + mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Returns the values that this ValueAnimator animates between. These values are stored in + * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list + * of value objects instead. + * + * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the + * values, per property, that define the animation. + */ + public PropertyValuesHolder[] getValues() { + return mValues; + } + + /** + * This function is called immediately before processing the first animation + * frame of an animation. If there is a nonzero startDelay, the + * function is called after that delay ends. + * It takes care of the final initialization steps for the + * animation. + * + *

Overrides of this method should call the superclass method to ensure + * that internal mechanisms for the animation are set up correctly.

+ */ + void initAnimation() { + if (!mInitialized) { + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].init(); + } + mInitialized = true; + } + } + + + /** + * Sets the length of the animation. The default duration is 300 milliseconds. + * + * @param duration The length of the animation, in milliseconds. This value cannot + * be negative. + * @return ValueAnimator The object called with setDuration(). This return + * value makes it easier to compose statements together that construct and then set the + * duration, as in ValueAnimator.ofInt(0, 10).setDuration(500).start(). + */ + public ValueAnimator setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("Animators cannot have negative duration: " + + duration); + } + mDuration = duration; + return this; + } + + /** + * Gets the length of the animation. The default duration is 300 milliseconds. + * + * @return The length of the animation, in milliseconds. + */ + public long getDuration() { + return mDuration; + } + + /** + * Sets the position of the animation to the specified point in time. This time should + * be between 0 and the total duration of the animation, including any repetition. If + * the animation has not yet been started, then it will not advance forward after it is + * set to this time; it will simply set the time to this value and perform any appropriate + * actions based on that time. If the animation is already running, then setCurrentPlayTime() + * will set the current playing time to this value and continue playing from that point. + * + * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. + */ + public void setCurrentPlayTime(long playTime) { + initAnimation(); + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + if (mPlayingState != RUNNING) { + mSeekTime = playTime; + mPlayingState = SEEKED; + } + mStartTime = currentTime - playTime; + animationFrame(currentTime); + } + + /** + * Gets the current position of the animation in time, which is equal to the current + * time minus the time that the animation started. An animation that is not yet started will + * return a value of zero. + * + * @return The current position in time of the animation. + */ + public long getCurrentPlayTime() { + if (!mInitialized || mPlayingState == STOPPED) { + return 0; + } + return AnimationUtils.currentAnimationTimeMillis() - mStartTime; + } + + /** + * This custom, static handler handles the timing pulse that is shared by + * all active animations. This approach ensures that the setting of animation + * values will happen on the UI thread and that all animations will share + * the same times for calculating their values, which makes synchronizing + * animations possible. + * + */ + private static class AnimationHandler extends Handler { + /** + * There are only two messages that we care about: ANIMATION_START and + * ANIMATION_FRAME. The START message is sent when an animation's start() + * method is called. It cannot start synchronously when start() is called + * because the call may be on the wrong thread, and it would also not be + * synchronized with other animations because it would not start on a common + * timing pulse. So each animation sends a START message to the handler, which + * causes the handler to place the animation on the active animations queue and + * start processing frames for that animation. + * The FRAME message is the one that is sent over and over while there are any + * active animations to process. + */ + @Override + public void handleMessage(Message msg) { + boolean callAgain = true; + ArrayList animations = sAnimations.get(); + ArrayList delayedAnims = sDelayedAnims.get(); + switch (msg.what) { + // TODO: should we avoid sending frame message when starting if we + // were already running? + case ANIMATION_START: + ArrayList pendingAnimations = sPendingAnimations.get(); + if (animations.size() > 0 || delayedAnims.size() > 0) { + callAgain = false; + } + // pendingAnims holds any animations that have requested to be started + // We're going to clear sPendingAnimations, but starting animation may + // cause more to be added to the pending list (for example, if one animation + // starting triggers another starting). So we loop until sPendingAnimations + // is empty. + while (pendingAnimations.size() > 0) { + ArrayList pendingCopy = + (ArrayList) pendingAnimations.clone(); + pendingAnimations.clear(); + int count = pendingCopy.size(); + for (int i = 0; i < count; ++i) { + ValueAnimator anim = pendingCopy.get(i); + // If the animation has a startDelay, place it on the delayed list + if (anim.mStartDelay == 0) { + anim.startAnimation(); + } else { + delayedAnims.add(anim); + } + } + } + // fall through to process first frame of new animations + case ANIMATION_FRAME: + // currentTime holds the common time for all animations processed + // during this frame + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + ArrayList readyAnims = sReadyAnims.get(); + ArrayList endingAnims = sEndingAnims.get(); + + // First, process animations currently sitting on the delayed queue, adding + // them to the active animations if they are ready + int numDelayedAnims = delayedAnims.size(); + for (int i = 0; i < numDelayedAnims; ++i) { + ValueAnimator anim = delayedAnims.get(i); + if (anim.delayedAnimationFrame(currentTime)) { + readyAnims.add(anim); + } + } + int numReadyAnims = readyAnims.size(); + if (numReadyAnims > 0) { + for (int i = 0; i < numReadyAnims; ++i) { + ValueAnimator anim = readyAnims.get(i); + anim.startAnimation(); + anim.mRunning = true; + delayedAnims.remove(anim); + } + readyAnims.clear(); + } + + // Now process all active animations. The return value from animationFrame() + // tells the handler whether it should now be ended + int numAnims = animations.size(); + int i = 0; + while (i < numAnims) { + ValueAnimator anim = animations.get(i); + if (anim.animationFrame(currentTime)) { + endingAnims.add(anim); + } + if (animations.size() == numAnims) { + ++i; + } else { + // An animation might be canceled or ended by client code + // during the animation frame. Check to see if this happened by + // seeing whether the current index is the same as it was before + // calling animationFrame(). Another approach would be to copy + // animations to a temporary list and process that list instead, + // but that entails garbage and processing overhead that would + // be nice to avoid. + --numAnims; + endingAnims.remove(anim); + } + } + if (endingAnims.size() > 0) { + for (i = 0; i < endingAnims.size(); ++i) { + endingAnims.get(i).endAnimation(); + } + endingAnims.clear(); + } + + // If there are still active or delayed animations, call the handler again + // after the frameDelay + if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) { + sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - + (AnimationUtils.currentAnimationTimeMillis() - currentTime))); + } + break; + } + } + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + public long getStartDelay() { + return mStartDelay; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + public void setStartDelay(long startDelay) { + this.mStartDelay = startDelay; + } + + /** + * The amount of time, in milliseconds, between each frame of the animation. This is a + * requested time that the animation will attempt to honor, but the actual delay between + * frames may be different, depending on system load and capabilities. This is a static + * function because the same delay will be applied to all animations, since they are all + * run off of a single timing loop. + * + * @return the requested time between frames, in milliseconds + */ + public static long getFrameDelay() { + return sFrameDelay; + } + + /** + * The amount of time, in milliseconds, between each frame of the animation. This is a + * requested time that the animation will attempt to honor, but the actual delay between + * frames may be different, depending on system load and capabilities. This is a static + * function because the same delay will be applied to all animations, since they are all + * run off of a single timing loop. + * + * @param frameDelay the requested time between frames, in milliseconds + */ + public static void setFrameDelay(long frameDelay) { + sFrameDelay = frameDelay; + } + + /** + * The most recent value calculated by this ValueAnimator when there is just one + * property being animated. This value is only sensible while the animation is running. The main + * purpose for this read-only property is to retrieve the value from the ValueAnimator + * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which + * is called during each animation frame, immediately after the value is calculated. + * + * @return animatedValue The value most recently calculated by this ValueAnimator for + * the single property being animated. If there are several properties being animated + * (specified by several PropertyValuesHolder objects in the constructor), this function + * returns the animated value for the first of those objects. + */ + public Object getAnimatedValue() { + if (mValues != null && mValues.length > 0) { + return mValues[0].getAnimatedValue(); + } + // Shouldn't get here; should always have values unless ValueAnimator was set up wrong + return null; + } + + /** + * The most recent value calculated by this ValueAnimator for propertyName. + * The main purpose for this read-only property is to retrieve the value from the + * ValueAnimator during a call to + * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which + * is called during each animation frame, immediately after the value is calculated. + * + * @return animatedValue The value most recently calculated for the named property + * by this ValueAnimator. + */ + public Object getAnimatedValue(String propertyName) { + PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName); + if (valuesHolder != null) { + return valuesHolder.getAnimatedValue(); + } else { + // At least avoid crashing if called with bogus propertyName + return null; + } + } + + /** + * Sets how many times the animation should be repeated. If the repeat + * count is 0, the animation is never repeated. If the repeat count is + * greater than 0 or {@link #INFINITE}, the repeat mode will be taken + * into account. The repeat count is 0 by default. + * + * @param value the number of times the animation should be repeated + */ + public void setRepeatCount(int value) { + mRepeatCount = value; + } + /** + * Defines how many times the animation should repeat. The default value + * is 0. + * + * @return the number of times the animation should repeat, or {@link #INFINITE} + */ + public int getRepeatCount() { + return mRepeatCount; + } + + /** + * Defines what this animation should do when it reaches the end. This + * setting is applied only when the repeat count is either greater than + * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. + * + * @param value {@link #RESTART} or {@link #REVERSE} + */ + public void setRepeatMode(int value) { + mRepeatMode = value; + } + + /** + * Defines what this animation should do when it reaches the end. + * + * @return either one of {@link #REVERSE} or {@link #RESTART} + */ + public int getRepeatMode() { + return mRepeatMode; + } + + /** + * Adds a listener to the set of listeners that are sent update events through the life of + * an animation. This method is called on all listeners for every frame of the animation, + * after the values for the animation have been calculated. + * + * @param listener the listener to be added to the current set of listeners for this animation. + */ + public void addUpdateListener(AnimatorUpdateListener listener) { + if (mUpdateListeners == null) { + mUpdateListeners = new ArrayList(); + } + mUpdateListeners.add(listener); + } + + /** + * Removes all listeners from the set listening to frame updates for this animation. + */ + public void removeAllUpdateListeners() { + if (mUpdateListeners == null) { + return; + } + mUpdateListeners.clear(); + mUpdateListeners = null; + } + + /** + * Removes a listener from the set listening to frame updates for this animation. + * + * @param listener the listener to be removed from the current set of update listeners + * for this animation. + */ + public void removeUpdateListener(AnimatorUpdateListener listener) { + if (mUpdateListeners == null) { + return; + } + mUpdateListeners.remove(listener); + if (mUpdateListeners.size() == 0) { + mUpdateListeners = null; + } + } + + + /** + * The time interpolator used in calculating the elapsed fraction of this animation. The + * interpolator determines whether the animation runs with linear or non-linear motion, + * such as acceleration and deceleration. The default value is + * {@link android.view.animation.AccelerateDecelerateInterpolator} + * + * @param value the interpolator to be used by this animation. A value of null + * will result in linear interpolation. + */ + @Override + public void setInterpolator(/*Time*/Interpolator value) { + if (value != null) { + mInterpolator = value; + } else { + mInterpolator = new LinearInterpolator(); + } + } + + /** + * Returns the timing interpolator that this ValueAnimator uses. + * + * @return The timing interpolator for this ValueAnimator. + */ + public /*Time*/Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * The type evaluator to be used when calculating the animated values of this animation. + * The system will automatically assign a float or int evaluator based on the type + * of startValue and endValue in the constructor. But if these values + * are not one of these primitive types, or if different evaluation is desired (such as is + * necessary with int values that represent colors), a custom evaluator needs to be assigned. + * For example, when running an animation on color values, the {@link ArgbEvaluator} + * should be used to get correct RGB color interpolation. + * + *

If this ValueAnimator has only one set of values being animated between, this evaluator + * will be used for that set. If there are several sets of values being animated, which is + * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator + * is assigned just to the first PropertyValuesHolder object.

+ * + * @param value the evaluator to be used this animation + */ + public void setEvaluator(TypeEvaluator value) { + if (value != null && mValues != null && mValues.length > 0) { + mValues[0].setEvaluator(value); + } + } + + /** + * Start the animation playing. This version of start() takes a boolean flag that indicates + * whether the animation should play in reverse. The flag is usually false, but may be set + * to true if called from the reverse() method. + * + *

The animation started by calling this method will be run on the thread that called + * this method. This thread should have a Looper on it (a runtime exception will be thrown if + * this is not the case). Also, if the animation will animate + * properties of objects in the view hierarchy, then the calling thread should be the UI + * thread for that view hierarchy.

+ * + * @param playBackwards Whether the ValueAnimator should start playing in reverse. + */ + private void start(boolean playBackwards) { + if (Looper.myLooper() == null) { + throw new AndroidRuntimeException("Animators may only be run on Looper threads"); + } + mPlayingBackwards = playBackwards; + mCurrentIteration = 0; + mPlayingState = STOPPED; + mStarted = true; + mStartedDelay = false; + sPendingAnimations.get().add(this); + if (mStartDelay == 0) { + // This sets the initial value of the animation, prior to actually starting it running + setCurrentPlayTime(getCurrentPlayTime()); + mPlayingState = STOPPED; + mRunning = true; + + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this); + } + } + } + AnimationHandler animationHandler = sAnimationHandler.get(); + if (animationHandler == null) { + animationHandler = new AnimationHandler(); + sAnimationHandler.set(animationHandler); + } + animationHandler.sendEmptyMessage(ANIMATION_START); + } + + @Override + public void start() { + start(false); + } + + @Override + public void cancel() { + // Only cancel if the animation is actually running or has been started and is about + // to run + if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) || + sDelayedAnims.get().contains(this)) { + // Only notify listeners if the animator has actually started + if (mRunning && mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + endAnimation(); + } + } + + @Override + public void end() { + if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) { + // Special case if the animation has not yet started; get it ready for ending + mStartedDelay = false; + startAnimation(); + } else if (!mInitialized) { + initAnimation(); + } + // The final value set on the target varies, depending on whether the animation + // was supposed to repeat an odd number of times + if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) { + animateValue(0f); + } else { + animateValue(1f); + } + endAnimation(); + } + + @Override + public boolean isRunning() { + return (mPlayingState == RUNNING || mRunning); + } + + @Override + public boolean isStarted() { + return mStarted; + } + + /** + * Plays the ValueAnimator in reverse. If the animation is already running, + * it will stop itself and play backwards from the point reached when reverse was called. + * If the animation is not currently running, then it will start from the end and + * play backwards. This behavior is only set for the current animation; future playing + * of the animation will use the default behavior of playing forward. + */ + public void reverse() { + mPlayingBackwards = !mPlayingBackwards; + if (mPlayingState == RUNNING) { + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + long currentPlayTime = currentTime - mStartTime; + long timeLeft = mDuration - currentPlayTime; + mStartTime = currentTime - timeLeft; + } else { + start(true); + } + } + + /** + * Called internally to end an animation by removing it from the animations list. Must be + * called on the UI thread. + */ + private void endAnimation() { + sAnimations.get().remove(this); + sPendingAnimations.get().remove(this); + sDelayedAnims.get().remove(this); + mPlayingState = STOPPED; + if (mRunning && mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(this); + } + } + mRunning = false; + mStarted = false; + } + + /** + * Called internally to start an animation by adding it to the active animations list. Must be + * called on the UI thread. + */ + private void startAnimation() { + initAnimation(); + sAnimations.get().add(this); + if (mStartDelay > 0 && mListeners != null) { + // Listeners were already notified in start() if startDelay is 0; this is + // just for delayed animations + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this); + } + } + } + + /** + * Internal function called to process an animation frame on an animation that is currently + * sleeping through its startDelay phase. The return value indicates whether it + * should be woken up and put on the active animations queue. + * + * @param currentTime The current animation time, used to calculate whether the animation + * has exceeded its startDelay and should be started. + * @return True if the animation's startDelay has been exceeded and the animation + * should be added to the set of active animations. + */ + private boolean delayedAnimationFrame(long currentTime) { + if (!mStartedDelay) { + mStartedDelay = true; + mDelayStartTime = currentTime; + } else { + long deltaTime = currentTime - mDelayStartTime; + if (deltaTime > mStartDelay) { + // startDelay ended - start the anim and record the + // mStartTime appropriately + mStartTime = currentTime - (deltaTime - mStartDelay); + mPlayingState = RUNNING; + return true; + } + } + return false; + } + + /** + * This internal function processes a single animation frame for a given animation. The + * currentTime parameter is the timing pulse sent by the handler, used to calculate the + * elapsed duration, and therefore + * the elapsed fraction, of the animation. The return value indicates whether the animation + * should be ended (which happens when the elapsed time of the animation exceeds the + * animation's duration, including the repeatCount). + * + * @param currentTime The current time, as tracked by the static timing handler + * @return true if the animation's duration, including any repetitions due to + * repeatCount has been exceeded and the animation should be ended. + */ + boolean animationFrame(long currentTime) { + boolean done = false; + + if (mPlayingState == STOPPED) { + mPlayingState = RUNNING; + if (mSeekTime < 0) { + mStartTime = currentTime; + } else { + mStartTime = currentTime - mSeekTime; + // Now that we're playing, reset the seek time + mSeekTime = -1; + } + } + switch (mPlayingState) { + case RUNNING: + case SEEKED: + float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; + if (fraction >= 1f) { + if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { + // Time to repeat + if (mListeners != null) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mListeners.get(i).onAnimationRepeat(this); + } + } + if (mRepeatMode == REVERSE) { + mPlayingBackwards = mPlayingBackwards ? false : true; + } + mCurrentIteration += (int)fraction; + fraction = fraction % 1f; + mStartTime += mDuration; + } else { + done = true; + fraction = Math.min(fraction, 1.0f); + } + } + if (mPlayingBackwards) { + fraction = 1f - fraction; + } + animateValue(fraction); + break; + } + + return done; + } + + /** + * Returns the current animation fraction, which is the elapsed/interpolated fraction used in + * the most recent frame update on the animation. + * + * @return Elapsed/interpolated fraction of the animation. + */ + public float getAnimatedFraction() { + return mCurrentFraction; + } + + /** + * This method is called with the elapsed fraction of the animation during every + * animation frame. This function turns the elapsed fraction into an interpolated fraction + * and then into an animated value (from the evaluator. The function is called mostly during + * animation updates, but it is also called when the end() + * function is called, to set the final value on the property. + * + *

Overrides of this method must call the superclass to perform the calculation + * of the animated value.

+ * + * @param fraction The elapsed fraction of the animation. + */ + void animateValue(float fraction) { + fraction = mInterpolator.getInterpolation(fraction); + mCurrentFraction = fraction; + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].calculateValue(fraction); + } + if (mUpdateListeners != null) { + int numListeners = mUpdateListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mUpdateListeners.get(i).onAnimationUpdate(this); + } + } + } + + @Override + public ValueAnimator clone() { + final ValueAnimator anim = (ValueAnimator) super.clone(); + if (mUpdateListeners != null) { + ArrayList oldListeners = mUpdateListeners; + anim.mUpdateListeners = new ArrayList(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mUpdateListeners.add(oldListeners.get(i)); + } + } + anim.mSeekTime = -1; + anim.mPlayingBackwards = false; + anim.mCurrentIteration = 0; + anim.mInitialized = false; + anim.mPlayingState = STOPPED; + anim.mStartedDelay = false; + PropertyValuesHolder[] oldValues = mValues; + if (oldValues != null) { + int numValues = oldValues.length; + anim.mValues = new PropertyValuesHolder[numValues]; + anim.mValuesMap = new HashMap(numValues); + for (int i = 0; i < numValues; ++i) { + PropertyValuesHolder newValuesHolder = oldValues[i].clone(); + anim.mValues[i] = newValuesHolder; + anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder); + } + } + return anim; + } + + /** + * Implementors of this interface can add themselves as update listeners + * to an ValueAnimator instance to receive callbacks on every animation + * frame, after the current frame's values have been calculated for that + * ValueAnimator. + */ + public static interface AnimatorUpdateListener { + /** + *

Notifies the occurrence of another frame of the animation.

+ * + * @param animation The animation which was repeated. + */ + void onAnimationUpdate(ValueAnimator animation); + + } + + /** + * Return the number of animations currently running. + * + * Used by StrictMode internally to annotate violations. Only + * called on the main thread. + * + * @hide + */ + public static int getCurrentAnimationsCount() { + return sAnimations.get().size(); + } + + /** + * Clear all animations on this thread, without canceling or ending them. + * This should be used with caution. + * + * @hide + */ + public static void clearAllAnimations() { + sAnimations.get().clear(); + sPendingAnimations.get().clear(); + sDelayedAnims.get().clear(); + } + + @Override + public String toString() { + String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode()); + if (mValues != null) { + for (int i = 0; i < mValues.length; ++i) { + returnVal += "\n " + mValues[i].toString(); + } + } + return returnVal; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/NineViewGroup.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/NineViewGroup.java new file mode 100644 index 000000000..7b830b9c0 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/NineViewGroup.java @@ -0,0 +1,79 @@ +package com.actionbarsherlock.internal.nineoldandroids.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorProxy; + +public abstract class NineViewGroup extends ViewGroup { + private final AnimatorProxy mProxy; + + public NineViewGroup(Context context) { + super(context); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + public NineViewGroup(Context context, AttributeSet attrs) { + super(context, attrs); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + public NineViewGroup(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + + @Override + public void setVisibility(int visibility) { + if (mProxy != null) { + if (visibility == GONE) { + clearAnimation(); + } else if (visibility == VISIBLE) { + setAnimation(mProxy); + } + } + super.setVisibility(visibility); + } + + public float getAlpha() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getAlpha(); + } else { + return super.getAlpha(); + } + } + public void setAlpha(float alpha) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setAlpha(alpha); + } else { + super.setAlpha(alpha); + } + } + public float getTranslationX() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getTranslationX(); + } else { + return super.getTranslationX(); + } + } + public void setTranslationX(float translationX) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setTranslationX(translationX); + } else { + super.setTranslationX(translationX); + } + } + public float getTranslationY() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getTranslationY(); + } else { + return super.getTranslationY(); + } + } + public void setTranslationY(float translationY) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setTranslationY(translationY); + } else { + super.setTranslationY(translationY); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/animation/AnimatorProxy.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/animation/AnimatorProxy.java new file mode 100644 index 000000000..e284604bb --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/view/animation/AnimatorProxy.java @@ -0,0 +1,205 @@ +package com.actionbarsherlock.internal.nineoldandroids.view.animation; + +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.os.Build; +import android.util.FloatMath; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +public final class AnimatorProxy extends Animation { + public static final boolean NEEDS_PROXY = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB; + + private static final WeakHashMap PROXIES = + new WeakHashMap(); + + public static AnimatorProxy wrap(View view) { + AnimatorProxy proxy = PROXIES.get(view); + if (proxy == null) { + proxy = new AnimatorProxy(view); + PROXIES.put(view, proxy); + } + return proxy; + } + + private final WeakReference mView; + + private float mAlpha = 1; + private float mScaleX = 1; + private float mScaleY = 1; + private float mTranslationX; + private float mTranslationY; + + private final RectF mBefore = new RectF(); + private final RectF mAfter = new RectF(); + private final Matrix mTempMatrix = new Matrix(); + + private AnimatorProxy(View view) { + setDuration(0); //perform transformation immediately + setFillAfter(true); //persist transformation beyond duration + view.setAnimation(this); + mView = new WeakReference(view); + } + + public float getAlpha() { + return mAlpha; + } + public void setAlpha(float alpha) { + if (mAlpha != alpha) { + mAlpha = alpha; + View view = mView.get(); + if (view != null) { + view.invalidate(); + } + } + } + public float getScaleX() { + return mScaleX; + } + public void setScaleX(float scaleX) { + if (mScaleX != scaleX) { + prepareForUpdate(); + mScaleX = scaleX; + invalidateAfterUpdate(); + } + } + public float getScaleY() { + return mScaleY; + } + public void setScaleY(float scaleY) { + if (mScaleY != scaleY) { + prepareForUpdate(); + mScaleY = scaleY; + invalidateAfterUpdate(); + } + } + public int getScrollX() { + View view = mView.get(); + if (view == null) { + return 0; + } + return view.getScrollX(); + } + public void setScrollX(int value) { + View view = mView.get(); + if (view != null) { + view.scrollTo(value, view.getScrollY()); + } + } + public int getScrollY() { + View view = mView.get(); + if (view == null) { + return 0; + } + return view.getScrollY(); + } + public void setScrollY(int value) { + View view = mView.get(); + if (view != null) { + view.scrollTo(view.getScrollY(), value); + } + } + + public float getTranslationX() { + return mTranslationX; + } + public void setTranslationX(float translationX) { + if (mTranslationX != translationX) { + prepareForUpdate(); + mTranslationX = translationX; + invalidateAfterUpdate(); + } + } + public float getTranslationY() { + return mTranslationY; + } + public void setTranslationY(float translationY) { + if (mTranslationY != translationY) { + prepareForUpdate(); + mTranslationY = translationY; + invalidateAfterUpdate(); + } + } + + private void prepareForUpdate() { + View view = mView.get(); + if (view != null) { + computeRect(mBefore, view); + } + } + private void invalidateAfterUpdate() { + View view = mView.get(); + if (view == null) { + return; + } + View parent = (View)view.getParent(); + if (parent == null) { + return; + } + + final RectF after = mAfter; + computeRect(after, view); + after.union(mBefore); + + parent.invalidate( + (int) FloatMath.floor(after.left), + (int) FloatMath.floor(after.top), + (int) FloatMath.ceil(after.right), + (int) FloatMath.ceil(after.bottom)); + } + + private void computeRect(final RectF r, View view) { + // compute current rectangle according to matrix transformation + final float w = view.getWidth(); + final float h = view.getHeight(); + + // use a rectangle at 0,0 to make sure we don't run into issues with scaling + r.set(0, 0, w, h); + + final Matrix m = mTempMatrix; + m.reset(); + transformMatrix(m, view); + mTempMatrix.mapRect(r); + + r.offset(view.getLeft(), view.getTop()); + + // Straighten coords if rotations flipped them + if (r.right < r.left) { + final float f = r.right; + r.right = r.left; + r.left = f; + } + if (r.bottom < r.top) { + final float f = r.top; + r.top = r.bottom; + r.bottom = f; + } + } + + private void transformMatrix(Matrix m, View view) { + final float w = view.getWidth(); + final float h = view.getHeight(); + + final float sX = mScaleX; + final float sY = mScaleY; + if ((sX != 1.0f) || (sY != 1.0f)) { + final float deltaSX = ((sX * w) - w) / 2f; + final float deltaSY = ((sY * h) - h) / 2f; + m.postScale(sX, sY); + m.postTranslate(-deltaSX, -deltaSY); + } + m.postTranslate(mTranslationX, mTranslationY); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + View view = mView.get(); + if (view != null) { + t.setAlpha(mAlpha); + transformMatrix(t.getMatrix(), view); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java new file mode 100644 index 000000000..2c428e907 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java @@ -0,0 +1,65 @@ +package com.actionbarsherlock.internal.nineoldandroids.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorProxy; + +public class NineFrameLayout extends FrameLayout { + private final AnimatorProxy mProxy; + + public NineFrameLayout(Context context) { + super(context); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + public NineFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + public NineFrameLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + + @Override + public void setVisibility(int visibility) { + if (mProxy != null) { + if (visibility == GONE) { + clearAnimation(); + } else if (visibility == VISIBLE) { + setAnimation(mProxy); + } + } + super.setVisibility(visibility); + } + + public float getAlpha() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getAlpha(); + } else { + return super.getAlpha(); + } + } + public void setAlpha(float alpha) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setAlpha(alpha); + } else { + super.setAlpha(alpha); + } + } + public float getTranslationY() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getTranslationY(); + } else { + return super.getTranslationY(); + } + } + public void setTranslationY(float translationY) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setTranslationY(translationY); + } else { + super.setTranslationY(translationY); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java new file mode 100644 index 000000000..a670b1f64 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java @@ -0,0 +1,65 @@ +package com.actionbarsherlock.internal.nineoldandroids.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorProxy; + +public class NineLinearLayout extends LinearLayout { + private final AnimatorProxy mProxy; + + public NineLinearLayout(Context context) { + super(context); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + public NineLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + public NineLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null; + } + + @Override + public void setVisibility(int visibility) { + if (mProxy != null) { + if (visibility == GONE) { + clearAnimation(); + } else if (visibility == VISIBLE) { + setAnimation(mProxy); + } + } + super.setVisibility(visibility); + } + + public float getAlpha() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getAlpha(); + } else { + return super.getAlpha(); + } + } + public void setAlpha(float alpha) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setAlpha(alpha); + } else { + super.setAlpha(alpha); + } + } + public float getTranslationX() { + if (AnimatorProxy.NEEDS_PROXY) { + return mProxy.getTranslationX(); + } else { + return super.getTranslationX(); + } + } + public void setTranslationX(float translationX) { + if (AnimatorProxy.NEEDS_PROXY) { + mProxy.setTranslationX(translationX); + } else { + super.setTranslationX(translationX); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/ActionProviderWrapper.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/ActionProviderWrapper.java new file mode 100644 index 000000000..b136d50f0 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/ActionProviderWrapper.java @@ -0,0 +1,40 @@ +package com.actionbarsherlock.internal.view; + +import com.actionbarsherlock.internal.view.menu.SubMenuWrapper; +import com.actionbarsherlock.view.ActionProvider; +import android.view.View; + +public class ActionProviderWrapper extends android.view.ActionProvider { + private final ActionProvider mProvider; + + + public ActionProviderWrapper(ActionProvider provider) { + super(null/*TODO*/); //XXX this *should* be unused + mProvider = provider; + } + + + public ActionProvider unwrap() { + return mProvider; + } + + @Override + public View onCreateActionView() { + return mProvider.onCreateActionView(); + } + + @Override + public boolean hasSubMenu() { + return mProvider.hasSubMenu(); + } + + @Override + public boolean onPerformDefaultAction() { + return mProvider.onPerformDefaultAction(); + } + + @Override + public void onPrepareSubMenu(android.view.SubMenu subMenu) { + mProvider.onPrepareSubMenu(new SubMenuWrapper(subMenu)); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/StandaloneActionMode.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/StandaloneActionMode.java new file mode 100644 index 000000000..0a87bd3f7 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/StandaloneActionMode.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.actionbarsherlock.internal.view; + +import android.content.Context; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import java.lang.ref.WeakReference; + +import com.actionbarsherlock.internal.view.menu.MenuBuilder; +import com.actionbarsherlock.internal.view.menu.MenuPopupHelper; +import com.actionbarsherlock.internal.view.menu.SubMenuBuilder; +import com.actionbarsherlock.internal.widget.ActionBarContextView; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +public class StandaloneActionMode extends ActionMode implements MenuBuilder.Callback { + private Context mContext; + private ActionBarContextView mContextView; + private ActionMode.Callback mCallback; + private WeakReference mCustomView; + private boolean mFinished; + private boolean mFocusable; + + private MenuBuilder mMenu; + + public StandaloneActionMode(Context context, ActionBarContextView view, + ActionMode.Callback callback, boolean isFocusable) { + mContext = context; + mContextView = view; + mCallback = callback; + + mMenu = new MenuBuilder(context).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + mMenu.setCallback(this); + mFocusable = isFocusable; + } + + @Override + public void setTitle(CharSequence title) { + mContextView.setTitle(title); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mContextView.setSubtitle(subtitle); + } + + @Override + public void setTitle(int resId) { + setTitle(mContext.getString(resId)); + } + + @Override + public void setSubtitle(int resId) { + setSubtitle(mContext.getString(resId)); + } + + @Override + public void setCustomView(View view) { + mContextView.setCustomView(view); + mCustomView = view != null ? new WeakReference(view) : null; + } + + @Override + public void invalidate() { + mCallback.onPrepareActionMode(this, mMenu); + } + + @Override + public void finish() { + if (mFinished) { + return; + } + mFinished = true; + + mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + mCallback.onDestroyActionMode(this); + } + + @Override + public Menu getMenu() { + return mMenu; + } + + @Override + public CharSequence getTitle() { + return mContextView.getTitle(); + } + + @Override + public CharSequence getSubtitle() { + return mContextView.getSubtitle(); + } + + @Override + public View getCustomView() { + return mCustomView != null ? mCustomView.get() : null; + } + + @Override + public MenuInflater getMenuInflater() { + return new MenuInflater(mContext); + } + + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + return mCallback.onActionItemClicked(this, item); + } + + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + } + + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + if (!subMenu.hasVisibleItems()) { + return true; + } + + new MenuPopupHelper(mContext, subMenu).show(); + return true; + } + + public void onCloseSubMenu(SubMenuBuilder menu) { + } + + public void onMenuModeChange(MenuBuilder menu) { + invalidate(); + mContextView.showOverflowMenu(); + } + + public boolean isUiFocusable() { + return mFocusable; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java new file mode 100644 index 000000000..7d45e81be --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java @@ -0,0 +1,6 @@ +package com.actionbarsherlock.internal.view; + +public interface View_HasStateListenerSupport { + void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener); + void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java new file mode 100644 index 000000000..3869d3290 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java @@ -0,0 +1,8 @@ +package com.actionbarsherlock.internal.view; + +import android.view.View; + +public interface View_OnAttachStateChangeListener { + void onViewAttachedToWindow(View v); + void onViewDetachedFromWindow(View v); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java new file mode 100644 index 000000000..0354ad1ad --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.view.KeyEvent; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +/** + * @hide + */ +public class ActionMenu implements Menu { + private Context mContext; + + private boolean mIsQwerty; + + private ArrayList mItems; + + public ActionMenu(Context context) { + mContext = context; + mItems = new ArrayList(); + } + + public Context getContext() { + return mContext; + } + + public MenuItem add(CharSequence title) { + return add(0, 0, 0, title); + } + + public MenuItem add(int titleRes) { + return add(0, 0, 0, titleRes); + } + + public MenuItem add(int groupId, int itemId, int order, int titleRes) { + return add(groupId, itemId, order, mContext.getResources().getString(titleRes)); + } + + public MenuItem add(int groupId, int itemId, int order, CharSequence title) { + ActionMenuItem item = new ActionMenuItem(getContext(), + groupId, itemId, 0, order, title); + mItems.add(order, item); + return item; + } + + public int addIntentOptions(int groupId, int itemId, int order, + ComponentName caller, Intent[] specifics, Intent intent, int flags, + MenuItem[] outSpecificItems) { + PackageManager pm = mContext.getPackageManager(); + final List lri = + pm.queryIntentActivityOptions(caller, specifics, intent, 0); + final int N = lri != null ? lri.size() : 0; + + if ((flags & FLAG_APPEND_TO_GROUP) == 0) { + removeGroup(groupId); + } + + for (int i=0; i= 0) { + outSpecificItems[ri.specificIndex] = item; + } + } + + return N; + } + + public SubMenu addSubMenu(CharSequence title) { + // TODO Implement submenus + return null; + } + + public SubMenu addSubMenu(int titleRes) { + // TODO Implement submenus + return null; + } + + public SubMenu addSubMenu(int groupId, int itemId, int order, + CharSequence title) { + // TODO Implement submenus + return null; + } + + public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) { + // TODO Implement submenus + return null; + } + + public void clear() { + mItems.clear(); + } + + public void close() { + } + + private int findItemIndex(int id) { + final ArrayList items = mItems; + final int itemCount = items.size(); + for (int i = 0; i < itemCount; i++) { + if (items.get(i).getItemId() == id) { + return i; + } + } + + return -1; + } + + public MenuItem findItem(int id) { + return mItems.get(findItemIndex(id)); + } + + public MenuItem getItem(int index) { + return mItems.get(index); + } + + public boolean hasVisibleItems() { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + if (items.get(i).isVisible()) { + return true; + } + } + + return false; + } + + private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) { + // TODO Make this smarter. + final boolean qwerty = mIsQwerty; + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + final char shortcut = qwerty ? item.getAlphabeticShortcut() : + item.getNumericShortcut(); + if (keyCode == shortcut) { + return item; + } + } + return null; + } + + public boolean isShortcutKey(int keyCode, KeyEvent event) { + return findItemWithShortcut(keyCode, event) != null; + } + + public boolean performIdentifierAction(int id, int flags) { + final int index = findItemIndex(id); + if (index < 0) { + return false; + } + + return mItems.get(index).invoke(); + } + + public boolean performShortcut(int keyCode, KeyEvent event, int flags) { + ActionMenuItem item = findItemWithShortcut(keyCode, event); + if (item == null) { + return false; + } + + return item.invoke(); + } + + public void removeGroup(int groupId) { + final ArrayList items = mItems; + int itemCount = items.size(); + int i = 0; + while (i < itemCount) { + if (items.get(i).getGroupId() == groupId) { + items.remove(i); + itemCount--; + } else { + i++; + } + } + } + + public void removeItem(int id) { + mItems.remove(findItemIndex(id)); + } + + public void setGroupCheckable(int group, boolean checkable, + boolean exclusive) { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + if (item.getGroupId() == group) { + item.setCheckable(checkable); + item.setExclusiveCheckable(exclusive); + } + } + } + + public void setGroupEnabled(int group, boolean enabled) { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + if (item.getGroupId() == group) { + item.setEnabled(enabled); + } + } + } + + public void setGroupVisible(int group, boolean visible) { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + if (item.getGroupId() == group) { + item.setVisible(visible); + } + } + } + + public void setQwertyMode(boolean isQwerty) { + mIsQwerty = isQwerty; + } + + public int size() { + return mItems.size(); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java new file mode 100644 index 000000000..510b97488 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; + +import com.actionbarsherlock.view.ActionProvider; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +/** + * @hide + */ +public class ActionMenuItem implements MenuItem { + private final int mId; + private final int mGroup; + //UNUSED private final int mCategoryOrder; + private final int mOrdering; + + private CharSequence mTitle; + private CharSequence mTitleCondensed; + private Intent mIntent; + private char mShortcutNumericChar; + private char mShortcutAlphabeticChar; + + private Drawable mIconDrawable; + //UNUSED private int mIconResId = NO_ICON; + + private Context mContext; + + private MenuItem.OnMenuItemClickListener mClickListener; + + //UNUSED private static final int NO_ICON = 0; + + private int mFlags = ENABLED; + private static final int CHECKABLE = 0x00000001; + private static final int CHECKED = 0x00000002; + private static final int EXCLUSIVE = 0x00000004; + private static final int HIDDEN = 0x00000008; + private static final int ENABLED = 0x00000010; + + public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering, + CharSequence title) { + mContext = context; + mId = id; + mGroup = group; + //UNUSED mCategoryOrder = categoryOrder; + mOrdering = ordering; + mTitle = title; + } + + public char getAlphabeticShortcut() { + return mShortcutAlphabeticChar; + } + + public int getGroupId() { + return mGroup; + } + + public Drawable getIcon() { + return mIconDrawable; + } + + public Intent getIntent() { + return mIntent; + } + + public int getItemId() { + return mId; + } + + public ContextMenuInfo getMenuInfo() { + return null; + } + + public char getNumericShortcut() { + return mShortcutNumericChar; + } + + public int getOrder() { + return mOrdering; + } + + public SubMenu getSubMenu() { + return null; + } + + public CharSequence getTitle() { + return mTitle; + } + + public CharSequence getTitleCondensed() { + return mTitleCondensed; + } + + public boolean hasSubMenu() { + return false; + } + + public boolean isCheckable() { + return (mFlags & CHECKABLE) != 0; + } + + public boolean isChecked() { + return (mFlags & CHECKED) != 0; + } + + public boolean isEnabled() { + return (mFlags & ENABLED) != 0; + } + + public boolean isVisible() { + return (mFlags & HIDDEN) == 0; + } + + public MenuItem setAlphabeticShortcut(char alphaChar) { + mShortcutAlphabeticChar = alphaChar; + return this; + } + + public MenuItem setCheckable(boolean checkable) { + mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0); + return this; + } + + public ActionMenuItem setExclusiveCheckable(boolean exclusive) { + mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0); + return this; + } + + public MenuItem setChecked(boolean checked) { + mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0); + return this; + } + + public MenuItem setEnabled(boolean enabled) { + mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0); + return this; + } + + public MenuItem setIcon(Drawable icon) { + mIconDrawable = icon; + //UNUSED mIconResId = NO_ICON; + return this; + } + + public MenuItem setIcon(int iconRes) { + //UNUSED mIconResId = iconRes; + mIconDrawable = mContext.getResources().getDrawable(iconRes); + return this; + } + + public MenuItem setIntent(Intent intent) { + mIntent = intent; + return this; + } + + public MenuItem setNumericShortcut(char numericChar) { + mShortcutNumericChar = numericChar; + return this; + } + + public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) { + mClickListener = menuItemClickListener; + return this; + } + + public MenuItem setShortcut(char numericChar, char alphaChar) { + mShortcutNumericChar = numericChar; + mShortcutAlphabeticChar = alphaChar; + return this; + } + + public MenuItem setTitle(CharSequence title) { + mTitle = title; + return this; + } + + public MenuItem setTitle(int title) { + mTitle = mContext.getResources().getString(title); + return this; + } + + public MenuItem setTitleCondensed(CharSequence title) { + mTitleCondensed = title; + return this; + } + + public MenuItem setVisible(boolean visible) { + mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN); + return this; + } + + public boolean invoke() { + if (mClickListener != null && mClickListener.onMenuItemClick(this)) { + return true; + } + + if (mIntent != null) { + mContext.startActivity(mIntent); + return true; + } + + return false; + } + + public void setShowAsAction(int show) { + // Do nothing. ActionMenuItems always show as action buttons. + } + + public MenuItem setActionView(View actionView) { + throw new UnsupportedOperationException(); + } + + public View getActionView() { + return null; + } + + @Override + public MenuItem setActionView(int resId) { + throw new UnsupportedOperationException(); + } + + @Override + public ActionProvider getActionProvider() { + return null; + } + + @Override + public MenuItem setActionProvider(ActionProvider actionProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public MenuItem setShowAsActionFlags(int actionEnum) { + setShowAsAction(actionEnum); + return this; + } + + @Override + public boolean expandActionView() { + return false; + } + + @Override + public boolean collapseActionView() { + return false; + } + + @Override + public boolean isActionViewExpanded() { + return false; + } + + @Override + public MenuItem setOnActionExpandListener(OnActionExpandListener listener) { + // No need to save the listener; ActionMenuItem does not support collapsing items. + return this; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java new file mode 100644 index 000000000..dcb50f362 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import java.util.HashSet; +import java.util.Set; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.internal.view.View_HasStateListenerSupport; +import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener; +import com.actionbarsherlock.internal.widget.CapitalizingButton; + +import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean; + +/** + * @hide + */ +public class ActionMenuItemView extends LinearLayout + implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener, + ActionMenuView.ActionMenuChildView, View_HasStateListenerSupport { + //UNUSED private static final String TAG = "ActionMenuItemView"; + + private MenuItemImpl mItemData; + private CharSequence mTitle; + private MenuBuilder.ItemInvoker mItemInvoker; + + private ImageButton mImageButton; + private CapitalizingButton mTextButton; + private boolean mAllowTextWithIcon; + private boolean mExpandedFormat; + private int mMinWidth; + + private final Set mListeners = new HashSet(); + + public ActionMenuItemView(Context context) { + this(context, null); + } + + public ActionMenuItemView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) { + //TODO super(context, attrs, defStyle); + super(context, attrs); + mAllowTextWithIcon = getResources_getBoolean(context, + R.bool.abs__config_allowActionMenuItemTextWithIcon); + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SherlockActionMenuItemView, 0, 0); + mMinWidth = a.getDimensionPixelSize( + R.styleable.SherlockActionMenuItemView_android_minWidth, 0); + a.recycle(); + } + + @Override + public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) { + mListeners.add(listener); + } + + @Override + public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) { + mListeners.remove(listener); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + for (View_OnAttachStateChangeListener listener : mListeners) { + listener.onViewAttachedToWindow(this); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + for (View_OnAttachStateChangeListener listener : mListeners) { + listener.onViewDetachedFromWindow(this); + } + } + + @Override + public void onFinishInflate() { + + mImageButton = (ImageButton) findViewById(R.id.abs__imageButton); + mTextButton = (CapitalizingButton) findViewById(R.id.abs__textButton); + mImageButton.setOnClickListener(this); + mTextButton.setOnClickListener(this); + mImageButton.setOnLongClickListener(this); + setOnClickListener(this); + setOnLongClickListener(this); + } + + public MenuItemImpl getItemData() { + return mItemData; + } + + public void initialize(MenuItemImpl itemData, int menuType) { + mItemData = itemData; + + setIcon(itemData.getIcon()); + setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon + setId(itemData.getItemId()); + + setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); + setEnabled(itemData.isEnabled()); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + mImageButton.setEnabled(enabled); + mTextButton.setEnabled(enabled); + } + + public void onClick(View v) { + if (mItemInvoker != null) { + mItemInvoker.invokeItem(mItemData); + } + } + + public void setItemInvoker(MenuBuilder.ItemInvoker invoker) { + mItemInvoker = invoker; + } + + public boolean prefersCondensedTitle() { + return true; + } + + public void setCheckable(boolean checkable) { + // TODO Support checkable action items + } + + public void setChecked(boolean checked) { + // TODO Support checkable action items + } + + public void setExpandedFormat(boolean expandedFormat) { + if (mExpandedFormat != expandedFormat) { + mExpandedFormat = expandedFormat; + if (mItemData != null) { + mItemData.actionFormatChanged(); + } + } + } + + private void updateTextButtonVisibility() { + boolean visible = !TextUtils.isEmpty(mTextButton.getText()); + visible &= mImageButton.getDrawable() == null || + (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat)); + + mTextButton.setVisibility(visible ? VISIBLE : GONE); + } + + public void setIcon(Drawable icon) { + mImageButton.setImageDrawable(icon); + if (icon != null) { + mImageButton.setVisibility(VISIBLE); + } else { + mImageButton.setVisibility(GONE); + } + + updateTextButtonVisibility(); + } + + public boolean hasText() { + return mTextButton.getVisibility() != GONE; + } + + public void setShortcut(boolean showShortcut, char shortcutKey) { + // Action buttons don't show text for shortcut keys. + } + + public void setTitle(CharSequence title) { + mTitle = title; + + mTextButton.setTextCompat(mTitle); + + setContentDescription(mTitle); + updateTextButtonVisibility(); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + onPopulateAccessibilityEvent(event); + return true; + } + + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + super.onPopulateAccessibilityEvent(event); + } + final CharSequence cdesc = getContentDescription(); + if (!TextUtils.isEmpty(cdesc)) { + event.getText().add(cdesc); + } + } + + @Override + public boolean dispatchHoverEvent(MotionEvent event) { + // Don't allow children to hover; we want this to be treated as a single component. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return onHoverEvent(event); + } + return false; + } + + public boolean showsIcon() { + return true; + } + + public boolean needsDividerBefore() { + return hasText() && mItemData.getIcon() == null; + } + + public boolean needsDividerAfter() { + return hasText(); + } + + @Override + public boolean onLongClick(View v) { + if (hasText()) { + // Don't show the cheat sheet for items that already show text. + return false; + } + + final int[] screenPos = new int[2]; + final Rect displayFrame = new Rect(); + getLocationOnScreen(screenPos); + getWindowVisibleDisplayFrame(displayFrame); + + final Context context = getContext(); + final int width = getWidth(); + final int height = getHeight(); + final int midy = screenPos[1] + height / 2; + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + + Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT); + if (midy < displayFrame.height()) { + // Show along the top; follow action buttons + cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT, + screenWidth - screenPos[0] - width / 2, height); + } else { + // Show along the bottom center + cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height); + } + cheatSheet.show(); + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int specSize = MeasureSpec.getSize(widthMeasureSpec); + final int oldMeasuredWidth = getMeasuredWidth(); + final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth) + : mMinWidth; + + if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) { + // Remeasure at exactly the minimum width. + super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), + heightMeasureSpec); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java new file mode 100644 index 000000000..020b41d32 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseBooleanArray; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.ImageButton; +import com.actionbarsherlock.R; +import com.actionbarsherlock.internal.view.View_HasStateListenerSupport; +import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener; +import com.actionbarsherlock.internal.view.menu.ActionMenuView.ActionMenuChildView; +import com.actionbarsherlock.view.ActionProvider; +import com.actionbarsherlock.view.MenuItem; + +/** + * MenuPresenter for building action menus as seen in the action bar and action modes. + */ +public class ActionMenuPresenter extends BaseMenuPresenter + implements ActionProvider.SubUiVisibilityListener { + //UNUSED private static final String TAG = "ActionMenuPresenter"; + + private View mOverflowButton; + private boolean mReserveOverflow; + private boolean mReserveOverflowSet; + private int mWidthLimit; + private int mActionItemWidthLimit; + private int mMaxItems; + private boolean mMaxItemsSet; + private boolean mStrictWidthLimit; + private boolean mWidthLimitSet; + private boolean mExpandedActionViewsExclusive; + + private int mMinCellSize; + + // Group IDs that have been added as actions - used temporarily, allocated here for reuse. + private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray(); + + private View mScrapActionButtonView; + + private OverflowPopup mOverflowPopup; + private ActionButtonSubmenu mActionButtonPopup; + + private OpenOverflowRunnable mPostedOpenRunnable; + + final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); + int mOpenSubMenuId; + + public ActionMenuPresenter(Context context) { + super(context, R.layout.abs__action_menu_layout, + R.layout.abs__action_menu_item_layout); + } + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + super.initForMenu(context, menu); + + final Resources res = context.getResources(); + + if (!mReserveOverflowSet) { + mReserveOverflow = reserveOverflow(mContext); + } + + if (!mWidthLimitSet) { + mWidthLimit = res.getDisplayMetrics().widthPixels / 2; + } + + // Measure for initial configuration + if (!mMaxItemsSet) { + mMaxItems = getResources_getInteger(context, R.integer.abs__max_action_buttons); + } + + int width = mWidthLimit; + if (mReserveOverflow) { + if (mOverflowButton == null) { + mOverflowButton = new OverflowMenuButton(mSystemContext); + final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + mOverflowButton.measure(spec, spec); + } + width -= mOverflowButton.getMeasuredWidth(); + } else { + mOverflowButton = null; + } + + mActionItemWidthLimit = width; + + mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density); + + // Drop a scrap view as it may no longer reflect the proper context/config. + mScrapActionButtonView = null; + } + + public static boolean reserveOverflow(Context context) { + //Check for theme-forced overflow action item + TypedArray a = context.getTheme().obtainStyledAttributes(R.styleable.SherlockTheme); + boolean result = a.getBoolean(R.styleable.SherlockTheme_absForceOverflow, false); + a.recycle(); + if (result) { + return true; + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB); + } else { + return !ViewConfiguration.get(context).hasPermanentMenuKey(); + } + } + + public void onConfigurationChanged(Configuration newConfig) { + if (!mMaxItemsSet) { + mMaxItems = getResources_getInteger(mContext, + R.integer.abs__max_action_buttons); + if (mMenu != null) { + mMenu.onItemsChanged(true); + } + } + } + + public void setWidthLimit(int width, boolean strict) { + mWidthLimit = width; + mStrictWidthLimit = strict; + mWidthLimitSet = true; + } + + public void setReserveOverflow(boolean reserveOverflow) { + mReserveOverflow = reserveOverflow; + mReserveOverflowSet = true; + } + + public void setItemLimit(int itemCount) { + mMaxItems = itemCount; + mMaxItemsSet = true; + } + + public void setExpandedActionViewsExclusive(boolean isExclusive) { + mExpandedActionViewsExclusive = isExclusive; + } + + @Override + public MenuView getMenuView(ViewGroup root) { + MenuView result = super.getMenuView(root); + ((ActionMenuView) result).setPresenter(this); + return result; + } + + @Override + public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) { + View actionView = item.getActionView(); + if (actionView == null || item.hasCollapsibleActionView()) { + if (!(convertView instanceof ActionMenuItemView)) { + convertView = null; + } + actionView = super.getItemView(item, convertView, parent); + } + actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE); + + final ActionMenuView menuParent = (ActionMenuView) parent; + final ViewGroup.LayoutParams lp = actionView.getLayoutParams(); + if (!menuParent.checkLayoutParams(lp)) { + actionView.setLayoutParams(menuParent.generateLayoutParams(lp)); + } + return actionView; + } + + @Override + public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) { + itemView.initialize(item, 0); + + final ActionMenuView menuView = (ActionMenuView) mMenuView; + ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; + actionItemView.setItemInvoker(menuView); + } + + @Override + public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) { + return item.isActionButton(); + } + + @Override + public void updateMenuView(boolean cleared) { + super.updateMenuView(cleared); + + if (mMenu != null) { + final ArrayList actionItems = mMenu.getActionItems(); + final int count = actionItems.size(); + for (int i = 0; i < count; i++) { + final ActionProvider provider = actionItems.get(i).getActionProvider(); + if (provider != null) { + provider.setSubUiVisibilityListener(this); + } + } + } + + final ArrayList nonActionItems = mMenu != null ? + mMenu.getNonActionItems() : null; + + boolean hasOverflow = false; + if (mReserveOverflow && nonActionItems != null) { + final int count = nonActionItems.size(); + if (count == 1) { + hasOverflow = !nonActionItems.get(0).isActionViewExpanded(); + } else { + hasOverflow = count > 0; + } + } + + if (hasOverflow) { + if (mOverflowButton == null) { + mOverflowButton = new OverflowMenuButton(mSystemContext); + } + ViewGroup parent = (ViewGroup) mOverflowButton.getParent(); + if (parent != mMenuView) { + if (parent != null) { + parent.removeView(mOverflowButton); + } + ActionMenuView menuView = (ActionMenuView) mMenuView; + menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams()); + } + } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) { + ((ViewGroup) mMenuView).removeView(mOverflowButton); + } + + ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow); + } + + @Override + public boolean filterLeftoverView(ViewGroup parent, int childIndex) { + if (parent.getChildAt(childIndex) == mOverflowButton) return false; + return super.filterLeftoverView(parent, childIndex); + } + + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + if (!subMenu.hasVisibleItems()) return false; + + SubMenuBuilder topSubMenu = subMenu; + while (topSubMenu.getParentMenu() != mMenu) { + topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu(); + } + View anchor = findViewForItem(topSubMenu.getItem()); + if (anchor == null) { + if (mOverflowButton == null) return false; + anchor = mOverflowButton; + } + + mOpenSubMenuId = subMenu.getItem().getItemId(); + mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu); + mActionButtonPopup.setAnchorView(anchor); + mActionButtonPopup.show(); + super.onSubMenuSelected(subMenu); + return true; + } + + private View findViewForItem(MenuItem item) { + final ViewGroup parent = (ViewGroup) mMenuView; + if (parent == null) return null; + + final int count = parent.getChildCount(); + for (int i = 0; i < count; i++) { + final View child = parent.getChildAt(i); + if (child instanceof MenuView.ItemView && + ((MenuView.ItemView) child).getItemData() == item) { + return child; + } + } + return null; + } + + /** + * Display the overflow menu if one is present. + * @return true if the overflow menu was shown, false otherwise. + */ + public boolean showOverflowMenu() { + if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null && + mPostedOpenRunnable == null) { + OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true); + mPostedOpenRunnable = new OpenOverflowRunnable(popup); + // Post this for later; we might still need a layout for the anchor to be right. + ((View) mMenuView).post(mPostedOpenRunnable); + + // ActionMenuPresenter uses null as a callback argument here + // to indicate overflow is opening. + super.onSubMenuSelected(null); + + return true; + } + return false; + } + + /** + * Hide the overflow menu if it is currently showing. + * + * @return true if the overflow menu was hidden, false otherwise. + */ + public boolean hideOverflowMenu() { + if (mPostedOpenRunnable != null && mMenuView != null) { + ((View) mMenuView).removeCallbacks(mPostedOpenRunnable); + mPostedOpenRunnable = null; + return true; + } + + MenuPopupHelper popup = mOverflowPopup; + if (popup != null) { + popup.dismiss(); + return true; + } + return false; + } + + /** + * Dismiss all popup menus - overflow and submenus. + * @return true if popups were dismissed, false otherwise. (This can be because none were open.) + */ + public boolean dismissPopupMenus() { + boolean result = hideOverflowMenu(); + result |= hideSubMenus(); + return result; + } + + /** + * Dismiss all submenu popups. + * + * @return true if popups were dismissed, false otherwise. (This can be because none were open.) + */ + public boolean hideSubMenus() { + if (mActionButtonPopup != null) { + mActionButtonPopup.dismiss(); + return true; + } + return false; + } + + /** + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mOverflowPopup != null && mOverflowPopup.isShowing(); + } + + /** + * @return true if space has been reserved in the action menu for an overflow item. + */ + public boolean isOverflowReserved() { + return mReserveOverflow; + } + + public boolean flagActionItems() { + final ArrayList visibleItems = mMenu.getVisibleItems(); + final int itemsSize = visibleItems.size(); + int maxActions = mMaxItems; + int widthLimit = mActionItemWidthLimit; + final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final ViewGroup parent = (ViewGroup) mMenuView; + + int requiredItems = 0; + int requestedItems = 0; + int firstActionWidth = 0; + boolean hasOverflow = false; + for (int i = 0; i < itemsSize; i++) { + MenuItemImpl item = visibleItems.get(i); + if (item.requiresActionButton()) { + requiredItems++; + } else if (item.requestsActionButton()) { + requestedItems++; + } else { + hasOverflow = true; + } + if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) { + // Overflow everything if we have an expanded action view and we're + // space constrained. + maxActions = 0; + } + } + + // Reserve a spot for the overflow item if needed. + if (mReserveOverflow && + (hasOverflow || requiredItems + requestedItems > maxActions)) { + maxActions--; + } + maxActions -= requiredItems; + + final SparseBooleanArray seenGroups = mActionButtonGroups; + seenGroups.clear(); + + int cellSize = 0; + int cellsRemaining = 0; + if (mStrictWidthLimit) { + cellsRemaining = widthLimit / mMinCellSize; + final int cellSizeRemaining = widthLimit % mMinCellSize; + cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining; + } + + // Flag as many more requested items as will fit. + for (int i = 0; i < itemsSize; i++) { + MenuItemImpl item = visibleItems.get(i); + + if (item.requiresActionButton()) { + View v = getItemView(item, mScrapActionButtonView, parent); + if (mScrapActionButtonView == null) { + mScrapActionButtonView = v; + } + if (mStrictWidthLimit) { + cellsRemaining -= ActionMenuView.measureChildForCells(v, + cellSize, cellsRemaining, querySpec, 0); + } else { + v.measure(querySpec, querySpec); + } + final int measuredWidth = v.getMeasuredWidth(); + widthLimit -= measuredWidth; + if (firstActionWidth == 0) { + firstActionWidth = measuredWidth; + } + final int groupId = item.getGroupId(); + if (groupId != 0) { + seenGroups.put(groupId, true); + } + item.setIsActionButton(true); + } else if (item.requestsActionButton()) { + // Items in a group with other items that already have an action slot + // can break the max actions rule, but not the width limit. + final int groupId = item.getGroupId(); + final boolean inGroup = seenGroups.get(groupId); + boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 && + (!mStrictWidthLimit || cellsRemaining > 0); + + if (isAction) { + View v = getItemView(item, mScrapActionButtonView, parent); + if (mScrapActionButtonView == null) { + mScrapActionButtonView = v; + } + if (mStrictWidthLimit) { + final int cells = ActionMenuView.measureChildForCells(v, + cellSize, cellsRemaining, querySpec, 0); + cellsRemaining -= cells; + if (cells == 0) { + isAction = false; + } + } else { + v.measure(querySpec, querySpec); + } + final int measuredWidth = v.getMeasuredWidth(); + widthLimit -= measuredWidth; + if (firstActionWidth == 0) { + firstActionWidth = measuredWidth; + } + + if (mStrictWidthLimit) { + isAction &= widthLimit >= 0; + } else { + // Did this push the entire first item past the limit? + isAction &= widthLimit + firstActionWidth > 0; + } + } + + if (isAction && groupId != 0) { + seenGroups.put(groupId, true); + } else if (inGroup) { + // We broke the width limit. Demote the whole group, they all overflow now. + seenGroups.put(groupId, false); + for (int j = 0; j < i; j++) { + MenuItemImpl areYouMyGroupie = visibleItems.get(j); + if (areYouMyGroupie.getGroupId() == groupId) { + // Give back the action slot + if (areYouMyGroupie.isActionButton()) maxActions++; + areYouMyGroupie.setIsActionButton(false); + } + } + } + + if (isAction) maxActions--; + + item.setIsActionButton(isAction); + } + } + return true; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + dismissPopupMenus(); + super.onCloseMenu(menu, allMenusAreClosing); + } + + @Override + public Parcelable onSaveInstanceState() { + SavedState state = new SavedState(); + state.openSubMenuId = mOpenSubMenuId; + return state; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState saved = (SavedState) state; + if (saved.openSubMenuId > 0) { + MenuItem item = mMenu.findItem(saved.openSubMenuId); + if (item != null) { + SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); + onSubMenuSelected(subMenu); + } + } + } + + @Override + public void onSubUiVisibilityChanged(boolean isVisible) { + if (isVisible) { + // Not a submenu, but treat it like one. + super.onSubMenuSelected(null); + } else { + mMenu.close(false); + } + } + + private static class SavedState implements Parcelable { + public int openSubMenuId; + + SavedState() { + } + + SavedState(Parcel in) { + openSubMenuId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(openSubMenuId); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private class OverflowMenuButton extends ImageButton implements ActionMenuChildView, View_HasStateListenerSupport { + private final Set mListeners = new HashSet(); + + public OverflowMenuButton(Context context) { + super(context, null, R.attr.actionOverflowButtonStyle); + + setClickable(true); + setFocusable(true); + setVisibility(VISIBLE); + setEnabled(true); + } + + @Override + public boolean performClick() { + if (super.performClick()) { + return true; + } + + playSoundEffect(SoundEffectConstants.CLICK); + showOverflowMenu(); + return true; + } + + public boolean needsDividerBefore() { + return false; + } + + public boolean needsDividerAfter() { + return false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + for (View_OnAttachStateChangeListener listener : mListeners) { + listener.onViewAttachedToWindow(this); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + for (View_OnAttachStateChangeListener listener : mListeners) { + listener.onViewDetachedFromWindow(this); + } + } + + @Override + public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) { + mListeners.add(listener); + } + + @Override + public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) { + mListeners.remove(listener); + } + } + + private class OverflowPopup extends MenuPopupHelper { + public OverflowPopup(Context context, MenuBuilder menu, View anchorView, + boolean overflowOnly) { + super(context, menu, anchorView, overflowOnly); + setCallback(mPopupPresenterCallback); + } + + @Override + public void onDismiss() { + super.onDismiss(); + mMenu.close(); + mOverflowPopup = null; + } + } + + private class ActionButtonSubmenu extends MenuPopupHelper { + //UNUSED private SubMenuBuilder mSubMenu; + + public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) { + super(context, subMenu); + //UNUSED mSubMenu = subMenu; + + MenuItemImpl item = (MenuItemImpl) subMenu.getItem(); + if (!item.isActionButton()) { + // Give a reasonable anchor to nested submenus. + setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton); + } + + setCallback(mPopupPresenterCallback); + + boolean preserveIconSpacing = false; + final int count = subMenu.size(); + for (int i = 0; i < count; i++) { + MenuItem childItem = subMenu.getItem(i); + if (childItem.isVisible() && childItem.getIcon() != null) { + preserveIconSpacing = true; + break; + } + } + setForceShowIcon(preserveIconSpacing); + } + + @Override + public void onDismiss() { + super.onDismiss(); + mActionButtonPopup = null; + mOpenSubMenuId = 0; + } + } + + private class PopupPresenterCallback implements MenuPresenter.Callback { + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + if (subMenu == null) return false; + + mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId(); + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (menu instanceof SubMenuBuilder) { + ((SubMenuBuilder) menu).getRootMenu().close(false); + } + } + } + + private class OpenOverflowRunnable implements Runnable { + private OverflowPopup mPopup; + + public OpenOverflowRunnable(OverflowPopup popup) { + mPopup = popup; + } + + public void run() { + mMenu.changeMenuMode(); + final View menuView = (View) mMenuView; + if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) { + mOverflowPopup = mPopup; + } + mPostedOpenRunnable = null; + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java new file mode 100644 index 000000000..40ab73c50 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.actionbarsherlock.internal.view.menu; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.os.Build; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.widget.LinearLayout; +import com.actionbarsherlock.internal.widget.IcsLinearLayout; + +/** + * @hide + */ +public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemInvoker, MenuView { + //UNUSED private static final String TAG = "ActionMenuView"; + private static final boolean IS_FROYO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; + + static final int MIN_CELL_SIZE = 56; // dips + static final int GENERATED_ITEM_PADDING = 4; // dips + + private MenuBuilder mMenu; + + private boolean mReserveOverflow; + private ActionMenuPresenter mPresenter; + private boolean mFormatItems; + private int mFormatItemsWidth; + private int mMinCellSize; + private int mGeneratedItemPadding; + //UNUSED private int mMeasuredExtraWidth; + + private boolean mFirst = true; + + public ActionMenuView(Context context) { + this(context, null); + } + + public ActionMenuView(Context context, AttributeSet attrs) { + super(context, attrs); + setBaselineAligned(false); + final float density = context.getResources().getDisplayMetrics().density; + mMinCellSize = (int) (MIN_CELL_SIZE * density); + mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density); + } + + public void setPresenter(ActionMenuPresenter presenter) { + mPresenter = presenter; + } + + public boolean isExpandedFormat() { + return mFormatItems; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (IS_FROYO) { + super.onConfigurationChanged(newConfig); + } + mPresenter.updateMenuView(false); + + if (mPresenter != null && mPresenter.isOverflowMenuShowing()) { + mPresenter.hideOverflowMenu(); + mPresenter.showOverflowMenu(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + //Need to trigger a relayout since we may have been added extremely + //late in the initial rendering (e.g., when contained in a ViewPager). + //See: https://github.com/JakeWharton/ActionBarSherlock/issues/272 + if (!IS_FROYO && mFirst) { + mFirst = false; + requestLayout(); + return; + } + super.onDraw(canvas); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // If we've been given an exact size to match, apply special formatting during layout. + final boolean wasFormatted = mFormatItems; + mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY; + + if (wasFormatted != mFormatItems) { + mFormatItemsWidth = 0; // Reset this when switching modes + } + + // Special formatting can change whether items can fit as action buttons. + // Kick the menu and update presenters when this changes. + final int widthSize = MeasureSpec.getMode(widthMeasureSpec); + if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) { + mFormatItemsWidth = widthSize; + mMenu.onItemsChanged(true); + } + + if (mFormatItems) { + onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) { + // We already know the width mode is EXACTLY if we're here. + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + final int widthPadding = getPaddingLeft() + getPaddingRight(); + final int heightPadding = getPaddingTop() + getPaddingBottom(); + + widthSize -= widthPadding; + + // Divide the view into cells. + final int cellCount = widthSize / mMinCellSize; + final int cellSizeRemaining = widthSize % mMinCellSize; + + if (cellCount == 0) { + // Give up, nothing fits. + setMeasuredDimension(widthSize, 0); + return; + } + + final int cellSize = mMinCellSize + cellSizeRemaining / cellCount; + + int cellsRemaining = cellCount; + int maxChildHeight = 0; + int maxCellsUsed = 0; + int expandableItemCount = 0; + int visibleItemCount = 0; + boolean hasOverflow = false; + + // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64. + long smallestItemsAt = 0; + + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) continue; + + final boolean isGeneratedItem = child instanceof ActionMenuItemView; + visibleItemCount++; + + if (isGeneratedItem) { + // Reset padding for generated menu item views; it may change below + // and views are recycled. + child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0); + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.expanded = false; + lp.extraPixels = 0; + lp.cellsUsed = 0; + lp.expandable = false; + lp.leftMargin = 0; + lp.rightMargin = 0; + lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText(); + + // Overflow always gets 1 cell. No more, no less. + final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining; + + final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable, + heightMeasureSpec, heightPadding); + + maxCellsUsed = Math.max(maxCellsUsed, cellsUsed); + if (lp.expandable) expandableItemCount++; + if (lp.isOverflowButton) hasOverflow = true; + + cellsRemaining -= cellsUsed; + maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); + if (cellsUsed == 1) smallestItemsAt |= (1 << i); + } + + // When we have overflow and a single expanded (text) item, we want to try centering it + // visually in the available space even though overflow consumes some of it. + final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2; + + // Divide space for remaining cells if we have items that can expand. + // Try distributing whole leftover cells to smaller items first. + + boolean needsExpansion = false; + while (expandableItemCount > 0 && cellsRemaining > 0) { + int minCells = Integer.MAX_VALUE; + long minCellsAt = 0; // Bit locations are indices of relevant child views + int minCellsItemCount = 0; + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + // Don't try to expand items that shouldn't. + if (!lp.expandable) continue; + + // Mark indices of children that can receive an extra cell. + if (lp.cellsUsed < minCells) { + minCells = lp.cellsUsed; + minCellsAt = 1 << i; + minCellsItemCount = 1; + } else if (lp.cellsUsed == minCells) { + minCellsAt |= 1 << i; + minCellsItemCount++; + } + } + + // Items that get expanded will always be in the set of smallest items when we're done. + smallestItemsAt |= minCellsAt; + + if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop. + + // We have enough cells, all minimum size items will be incremented. + minCells++; + + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if ((minCellsAt & (1 << i)) == 0) { + // If this item is already at our small item count, mark it for later. + if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i; + continue; + } + + if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) { + // Add padding to this item such that it centers. + child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0); + } + lp.cellsUsed++; + lp.expanded = true; + cellsRemaining--; + } + + needsExpansion = true; + } + + // Divide any space left that wouldn't divide along cell boundaries + // evenly among the smallest items + + final boolean singleItem = !hasOverflow && visibleItemCount == 1; + if (cellsRemaining > 0 && smallestItemsAt != 0 && + (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) { + float expandCount = Long.bitCount(smallestItemsAt); + + if (!singleItem) { + // The items at the far edges may only expand by half in order to pin to either side. + if ((smallestItemsAt & 1) != 0) { + LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams(); + if (!lp.preventEdgeOffset) expandCount -= 0.5f; + } + if ((smallestItemsAt & (1 << (childCount - 1))) != 0) { + LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams()); + if (!lp.preventEdgeOffset) expandCount -= 0.5f; + } + } + + final int extraPixels = expandCount > 0 ? + (int) (cellsRemaining * cellSize / expandCount) : 0; + + for (int i = 0; i < childCount; i++) { + if ((smallestItemsAt & (1 << i)) == 0) continue; + + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (child instanceof ActionMenuItemView) { + // If this is one of our views, expand and measure at the larger size. + lp.extraPixels = extraPixels; + lp.expanded = true; + if (i == 0 && !lp.preventEdgeOffset) { + // First item gets part of its new padding pushed out of sight. + // The last item will get this implicitly from layout. + lp.leftMargin = -extraPixels / 2; + } + needsExpansion = true; + } else if (lp.isOverflowButton) { + lp.extraPixels = extraPixels; + lp.expanded = true; + lp.rightMargin = -extraPixels / 2; + needsExpansion = true; + } else { + // If we don't know what it is, give it some margins instead + // and let it center within its space. We still want to pin + // against the edges. + if (i != 0) { + lp.leftMargin = extraPixels / 2; + } + if (i != childCount - 1) { + lp.rightMargin = extraPixels / 2; + } + } + } + + cellsRemaining = 0; + } + + // Remeasure any items that have had extra space allocated to them. + if (needsExpansion) { + int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - heightPadding, heightMode); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (!lp.expanded) continue; + + final int width = lp.cellsUsed * cellSize + lp.extraPixels; + child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec); + } + } + + if (heightMode != MeasureSpec.EXACTLY) { + heightSize = maxChildHeight; + } + + setMeasuredDimension(widthSize, heightSize); + //UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize; + } + + /** + * Measure a child view to fit within cell-based formatting. The child's width + * will be measured to a whole multiple of cellSize. + * + *

Sets the expandable and cellsUsed fields of LayoutParams. + * + * @param child Child to measure + * @param cellSize Size of one cell + * @param cellsRemaining Number of cells remaining that this view can expand to fill + * @param parentHeightMeasureSpec MeasureSpec used by the parent view + * @param parentHeightPadding Padding present in the parent view + * @return Number of cells this child was measured to occupy + */ + static int measureChildForCells(View child, int cellSize, int cellsRemaining, + int parentHeightMeasureSpec, int parentHeightPadding) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) - + parentHeightPadding; + final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec); + final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode); + + int cellsUsed = 0; + if (cellsRemaining > 0) { + final int childWidthSpec = MeasureSpec.makeMeasureSpec( + cellSize * cellsRemaining, MeasureSpec.AT_MOST); + child.measure(childWidthSpec, childHeightSpec); + + final int measuredWidth = child.getMeasuredWidth(); + cellsUsed = measuredWidth / cellSize; + if (measuredWidth % cellSize != 0) cellsUsed++; + } + + final ActionMenuItemView itemView = child instanceof ActionMenuItemView ? + (ActionMenuItemView) child : null; + final boolean expandable = !lp.isOverflowButton && itemView != null && itemView.hasText(); + lp.expandable = expandable; + + lp.cellsUsed = cellsUsed; + final int targetWidth = cellsUsed * cellSize; + child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), + childHeightSpec); + return cellsUsed; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mFormatItems) { + super.onLayout(changed, left, top, right, bottom); + return; + } + + final int childCount = getChildCount(); + final int midVertical = (top + bottom) / 2; + final int dividerWidth = 0;//getDividerWidth(); + int overflowWidth = 0; + //UNUSED int nonOverflowWidth = 0; + int nonOverflowCount = 0; + int widthRemaining = right - left - getPaddingRight() - getPaddingLeft(); + boolean hasOverflow = false; + for (int i = 0; i < childCount; i++) { + final View v = getChildAt(i); + if (v.getVisibility() == GONE) { + continue; + } + + LayoutParams p = (LayoutParams) v.getLayoutParams(); + if (p.isOverflowButton) { + overflowWidth = v.getMeasuredWidth(); + if (hasDividerBeforeChildAt(i)) { + overflowWidth += dividerWidth; + } + + int height = v.getMeasuredHeight(); + int r = getWidth() - getPaddingRight() - p.rightMargin; + int l = r - overflowWidth; + int t = midVertical - (height / 2); + int b = t + height; + v.layout(l, t, r, b); + + widthRemaining -= overflowWidth; + hasOverflow = true; + } else { + final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin; + //UNUSED nonOverflowWidth += size; + widthRemaining -= size; + if (hasDividerBeforeChildAt(i)) { + //UNUSED nonOverflowWidth += dividerWidth; + } + nonOverflowCount++; + } + } + + if (childCount == 1 && !hasOverflow) { + // Center a single child + final View v = getChildAt(0); + final int width = v.getMeasuredWidth(); + final int height = v.getMeasuredHeight(); + final int midHorizontal = (right - left) / 2; + final int l = midHorizontal - width / 2; + final int t = midVertical - height / 2; + v.layout(l, t, l + width, t + height); + return; + } + + final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1); + final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0); + + int startLeft = getPaddingLeft(); + for (int i = 0; i < childCount; i++) { + final View v = getChildAt(i); + final LayoutParams lp = (LayoutParams) v.getLayoutParams(); + if (v.getVisibility() == GONE || lp.isOverflowButton) { + continue; + } + + startLeft += lp.leftMargin; + int width = v.getMeasuredWidth(); + int height = v.getMeasuredHeight(); + int t = midVertical - height / 2; + v.layout(startLeft, t, startLeft + width, t + height); + startLeft += width + lp.rightMargin + spacerSize; + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mPresenter.dismissPopupMenus(); + } + + public boolean isOverflowReserved() { + return mReserveOverflow; + } + + public void setOverflowReserved(boolean reserveOverflow) { + mReserveOverflow = reserveOverflow; + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + params.gravity = Gravity.CENTER_VERTICAL; + return params; + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + if (p instanceof LayoutParams) { + LayoutParams result = new LayoutParams((LayoutParams) p); + if (result.gravity <= Gravity.NO_GRAVITY) { + result.gravity = Gravity.CENTER_VERTICAL; + } + return result; + } + return generateDefaultLayoutParams(); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p != null && p instanceof LayoutParams; + } + + public LayoutParams generateOverflowButtonLayoutParams() { + LayoutParams result = generateDefaultLayoutParams(); + result.isOverflowButton = true; + return result; + } + + public boolean invokeItem(MenuItemImpl item) { + return mMenu.performItemAction(item, 0); + } + + public int getWindowAnimations() { + return 0; + } + + public void initialize(MenuBuilder menu) { + mMenu = menu; + } + + //@Override + protected boolean hasDividerBeforeChildAt(int childIndex) { + final View childBefore = getChildAt(childIndex - 1); + final View child = getChildAt(childIndex); + boolean result = false; + if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) { + result |= ((ActionMenuChildView) childBefore).needsDividerAfter(); + } + if (childIndex > 0 && child instanceof ActionMenuChildView) { + result |= ((ActionMenuChildView) child).needsDividerBefore(); + } + return result; + } + + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return false; + } + + public interface ActionMenuChildView { + public boolean needsDividerBefore(); + public boolean needsDividerAfter(); + } + + public static class LayoutParams extends LinearLayout.LayoutParams { + public boolean isOverflowButton; + public int cellsUsed; + public int extraPixels; + public boolean expandable; + public boolean preventEdgeOffset; + + public boolean expanded; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(LayoutParams other) { + super((LinearLayout.LayoutParams) other); + isOverflowButton = other.isOverflowButton; + } + + public LayoutParams(int width, int height) { + super(width, height); + isOverflowButton = false; + } + + public LayoutParams(int width, int height, boolean isOverflowButton) { + super(width, height); + this.isOverflowButton = isOverflowButton; + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java new file mode 100644 index 000000000..6da26f2ae --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import java.util.ArrayList; +import android.content.Context; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Base class for MenuPresenters that have a consistent container view and item + * views. Behaves similarly to an AdapterView in that existing item views will + * be reused if possible when items change. + */ +public abstract class BaseMenuPresenter implements MenuPresenter { + private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + + protected Context mSystemContext; + protected Context mContext; + protected MenuBuilder mMenu; + protected LayoutInflater mSystemInflater; + protected LayoutInflater mInflater; + private Callback mCallback; + + private int mMenuLayoutRes; + private int mItemLayoutRes; + + protected MenuView mMenuView; + + private int mId; + + /** + * Construct a new BaseMenuPresenter. + * + * @param context Context for generating system-supplied views + * @param menuLayoutRes Layout resource ID for the menu container view + * @param itemLayoutRes Layout resource ID for a single item view + */ + public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) { + mSystemContext = context; + mSystemInflater = LayoutInflater.from(context); + mMenuLayoutRes = menuLayoutRes; + mItemLayoutRes = itemLayoutRes; + } + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + mContext = context; + mInflater = LayoutInflater.from(mContext); + mMenu = menu; + } + + @Override + public MenuView getMenuView(ViewGroup root) { + if (mMenuView == null) { + mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false); + mMenuView.initialize(mMenu); + updateMenuView(true); + } + + return mMenuView; + } + + /** + * Reuses item views when it can + */ + public void updateMenuView(boolean cleared) { + final ViewGroup parent = (ViewGroup) mMenuView; + if (parent == null) return; + + int childIndex = 0; + if (mMenu != null) { + mMenu.flagActionItems(); + ArrayList visibleItems = mMenu.getVisibleItems(); + final int itemCount = visibleItems.size(); + for (int i = 0; i < itemCount; i++) { + MenuItemImpl item = visibleItems.get(i); + if (shouldIncludeItem(childIndex, item)) { + final View convertView = parent.getChildAt(childIndex); + final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ? + ((MenuView.ItemView) convertView).getItemData() : null; + final View itemView = getItemView(item, convertView, parent); + if (item != oldItem) { + // Don't let old states linger with new data. + itemView.setPressed(false); + if (IS_HONEYCOMB) itemView.jumpDrawablesToCurrentState(); + } + if (itemView != convertView) { + addItemView(itemView, childIndex); + } + childIndex++; + } + } + } + + // Remove leftover views. + while (childIndex < parent.getChildCount()) { + if (!filterLeftoverView(parent, childIndex)) { + childIndex++; + } + } + } + + /** + * Add an item view at the given index. + * + * @param itemView View to add + * @param childIndex Index within the parent to insert at + */ + protected void addItemView(View itemView, int childIndex) { + final ViewGroup currentParent = (ViewGroup) itemView.getParent(); + if (currentParent != null) { + currentParent.removeView(itemView); + } + ((ViewGroup) mMenuView).addView(itemView, childIndex); + } + + /** + * Filter the child view at index and remove it if appropriate. + * @param parent Parent to filter from + * @param childIndex Index to filter + * @return true if the child view at index was removed + */ + protected boolean filterLeftoverView(ViewGroup parent, int childIndex) { + parent.removeViewAt(childIndex); + return true; + } + + public void setCallback(Callback cb) { + mCallback = cb; + } + + /** + * Create a new item view that can be re-bound to other item data later. + * + * @return The new item view + */ + public MenuView.ItemView createItemView(ViewGroup parent) { + return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false); + } + + /** + * Prepare an item view for use. See AdapterView for the basic idea at work here. + * This may require creating a new item view, but well-behaved implementations will + * re-use the view passed as convertView if present. The returned view will be populated + * with data from the item parameter. + * + * @param item Item to present + * @param convertView Existing view to reuse + * @param parent Intended parent view - use for inflation. + * @return View that presents the requested menu item + */ + public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) { + MenuView.ItemView itemView; + if (convertView instanceof MenuView.ItemView) { + itemView = (MenuView.ItemView) convertView; + } else { + itemView = createItemView(parent); + } + bindItemView(item, itemView); + return (View) itemView; + } + + /** + * Bind item data to an existing item view. + * + * @param item Item to bind + * @param itemView View to populate with item data + */ + public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView); + + /** + * Filter item by child index and item data. + * + * @param childIndex Indended presentation index of this item + * @param item Item to present + * @return true if this item should be included in this menu presentation; false otherwise + */ + public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) { + return true; + } + + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mCallback != null) { + mCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + public boolean onSubMenuSelected(SubMenuBuilder menu) { + if (mCallback != null) { + return mCallback.onOpenSubMenu(menu); + } + return false; + } + + public boolean flagActionItems() { + return false; + } + + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + public int getId() { + return mId; + } + + public void setId(int id) { + mId = id; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java new file mode 100644 index 000000000..ac25c3736 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import com.actionbarsherlock.R; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.TextView; + +/** + * The item view for each item in the ListView-based MenuViews. + */ +public class ListMenuItemView extends LinearLayout implements MenuView.ItemView { + private MenuItemImpl mItemData; + + private ImageView mIconView; + private RadioButton mRadioButton; + private TextView mTitleView; + private CheckBox mCheckBox; + private TextView mShortcutView; + + private Drawable mBackground; + private int mTextAppearance; + private Context mTextAppearanceContext; + private boolean mPreserveIconSpacing; + + //UNUSED private int mMenuType; + + private LayoutInflater mInflater; + + private boolean mForceShowIcon; + + final Context mContext; + + public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + mContext = context; + + TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.SherlockMenuView, defStyle, 0); + + mBackground = a.getDrawable(R.styleable.SherlockMenuView_itemBackground); + mTextAppearance = a.getResourceId(R.styleable. + SherlockMenuView_itemTextAppearance, -1); + mPreserveIconSpacing = a.getBoolean( + R.styleable.SherlockMenuView_preserveIconSpacing, false); + mTextAppearanceContext = context; + + a.recycle(); + } + + public ListMenuItemView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + setBackgroundDrawable(mBackground); + + mTitleView = (TextView) findViewById(R.id.abs__title); + if (mTextAppearance != -1) { + mTitleView.setTextAppearance(mTextAppearanceContext, + mTextAppearance); + } + + mShortcutView = (TextView) findViewById(R.id.abs__shortcut); + } + + public void initialize(MenuItemImpl itemData, int menuType) { + mItemData = itemData; + //UNUSED mMenuType = menuType; + + setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); + + setTitle(itemData.getTitleForItemView(this)); + setCheckable(itemData.isCheckable()); + setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut()); + setIcon(itemData.getIcon()); + setEnabled(itemData.isEnabled()); + } + + public void setForceShowIcon(boolean forceShow) { + mPreserveIconSpacing = mForceShowIcon = forceShow; + } + + public void setTitle(CharSequence title) { + if (title != null) { + mTitleView.setText(title); + + if (mTitleView.getVisibility() != VISIBLE) mTitleView.setVisibility(VISIBLE); + } else { + if (mTitleView.getVisibility() != GONE) mTitleView.setVisibility(GONE); + } + } + + public MenuItemImpl getItemData() { + return mItemData; + } + + public void setCheckable(boolean checkable) { + + if (!checkable && mRadioButton == null && mCheckBox == null) { + return; + } + + if (mRadioButton == null) { + insertRadioButton(); + } + if (mCheckBox == null) { + insertCheckBox(); + } + + // Depending on whether its exclusive check or not, the checkbox or + // radio button will be the one in use (and the other will be otherCompoundButton) + final CompoundButton compoundButton; + final CompoundButton otherCompoundButton; + + if (mItemData.isExclusiveCheckable()) { + compoundButton = mRadioButton; + otherCompoundButton = mCheckBox; + } else { + compoundButton = mCheckBox; + otherCompoundButton = mRadioButton; + } + + if (checkable) { + compoundButton.setChecked(mItemData.isChecked()); + + final int newVisibility = checkable ? VISIBLE : GONE; + if (compoundButton.getVisibility() != newVisibility) { + compoundButton.setVisibility(newVisibility); + } + + // Make sure the other compound button isn't visible + if (otherCompoundButton.getVisibility() != GONE) { + otherCompoundButton.setVisibility(GONE); + } + } else { + mCheckBox.setVisibility(GONE); + mRadioButton.setVisibility(GONE); + } + } + + public void setChecked(boolean checked) { + CompoundButton compoundButton; + + if (mItemData.isExclusiveCheckable()) { + if (mRadioButton == null) { + insertRadioButton(); + } + compoundButton = mRadioButton; + } else { + if (mCheckBox == null) { + insertCheckBox(); + } + compoundButton = mCheckBox; + } + + compoundButton.setChecked(checked); + } + + public void setShortcut(boolean showShortcut, char shortcutKey) { + final int newVisibility = (showShortcut && mItemData.shouldShowShortcut()) + ? VISIBLE : GONE; + + if (newVisibility == VISIBLE) { + mShortcutView.setText(mItemData.getShortcutLabel()); + } + + if (mShortcutView.getVisibility() != newVisibility) { + mShortcutView.setVisibility(newVisibility); + } + } + + public void setIcon(Drawable icon) { + final boolean showIcon = mItemData.shouldShowIcon() || mForceShowIcon; + if (!showIcon && !mPreserveIconSpacing) { + return; + } + + if (mIconView == null && icon == null && !mPreserveIconSpacing) { + return; + } + + if (mIconView == null) { + insertIconView(); + } + + if (icon != null || mPreserveIconSpacing) { + mIconView.setImageDrawable(showIcon ? icon : null); + + if (mIconView.getVisibility() != VISIBLE) { + mIconView.setVisibility(VISIBLE); + } + } else { + mIconView.setVisibility(GONE); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mIconView != null && mPreserveIconSpacing) { + // Enforce minimum icon spacing + ViewGroup.LayoutParams lp = getLayoutParams(); + LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); + if (lp.height > 0 && iconLp.width <= 0) { + iconLp.width = lp.height; + } + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private void insertIconView() { + LayoutInflater inflater = getInflater(); + mIconView = (ImageView) inflater.inflate(R.layout.abs__list_menu_item_icon, + this, false); + addView(mIconView, 0); + } + + private void insertRadioButton() { + LayoutInflater inflater = getInflater(); + mRadioButton = + (RadioButton) inflater.inflate(R.layout.abs__list_menu_item_radio, + this, false); + addView(mRadioButton); + } + + private void insertCheckBox() { + LayoutInflater inflater = getInflater(); + mCheckBox = + (CheckBox) inflater.inflate(R.layout.abs__list_menu_item_checkbox, + this, false); + addView(mCheckBox); + } + + public boolean prefersCondensedTitle() { + return false; + } + + public boolean showsIcon() { + return mForceShowIcon; + } + + private LayoutInflater getInflater() { + if (mInflater == null) { + mInflater = LayoutInflater.from(mContext); + } + return mInflater; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java new file mode 100644 index 000000000..179b8f037 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java @@ -0,0 +1,1335 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.SparseArray; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.view.ActionProvider; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +/** + * Implementation of the {@link android.view.Menu} interface for creating a + * standard menu UI. + */ +public class MenuBuilder implements Menu { + //UNUSED private static final String TAG = "MenuBuilder"; + + private static final String PRESENTER_KEY = "android:menu:presenters"; + private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates"; + private static final String EXPANDED_ACTION_VIEW_ID = "android:menu:expandedactionview"; + + private static final int[] sCategoryToOrder = new int[] { + 1, /* No category */ + 4, /* CONTAINER */ + 5, /* SYSTEM */ + 3, /* SECONDARY */ + 2, /* ALTERNATIVE */ + 0, /* SELECTED_ALTERNATIVE */ + }; + + private final Context mContext; + private final Resources mResources; + + /** + * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode() + * instead of accessing this directly. + */ + private boolean mQwertyMode; + + /** + * Whether the shortcuts should be visible on menus. Use isShortcutsVisible() + * instead of accessing this directly. + */ + private boolean mShortcutsVisible; + + /** + * Callback that will receive the various menu-related events generated by + * this class. Use getCallback to get a reference to the callback. + */ + private Callback mCallback; + + /** Contains all of the items for this menu */ + private ArrayList mItems; + + /** Contains only the items that are currently visible. This will be created/refreshed from + * {@link #getVisibleItems()} */ + private ArrayList mVisibleItems; + /** + * Whether or not the items (or any one item's shown state) has changed since it was last + * fetched from {@link #getVisibleItems()} + */ + private boolean mIsVisibleItemsStale; + + /** + * Contains only the items that should appear in the Action Bar, if present. + */ + private ArrayList mActionItems; + /** + * Contains items that should NOT appear in the Action Bar, if present. + */ + private ArrayList mNonActionItems; + + /** + * Whether or not the items (or any one item's action state) has changed since it was + * last fetched. + */ + private boolean mIsActionItemsStale; + + /** + * Default value for how added items should show in the action list. + */ + private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER; + + /** + * Current use case is Context Menus: As Views populate the context menu, each one has + * extra information that should be passed along. This is the current menu info that + * should be set on all items added to this menu. + */ + private ContextMenuInfo mCurrentMenuInfo; + + /** Header title for menu types that have a header (context and submenus) */ + CharSequence mHeaderTitle; + /** Header icon for menu types that have a header and support icons (context) */ + Drawable mHeaderIcon; + /** Header custom view for menu types that have a header and support custom views (context) */ + View mHeaderView; + + /** + * Contains the state of the View hierarchy for all menu views when the menu + * was frozen. + */ + //UNUSED private SparseArray mFrozenViewStates; + + /** + * Prevents onItemsChanged from doing its junk, useful for batching commands + * that may individually call onItemsChanged. + */ + private boolean mPreventDispatchingItemsChanged = false; + private boolean mItemsChangedWhileDispatchPrevented = false; + + private boolean mOptionalIconsVisible = false; + + private boolean mIsClosing = false; + + private ArrayList mTempShortcutItemList = new ArrayList(); + + private CopyOnWriteArrayList> mPresenters = + new CopyOnWriteArrayList>(); + + /** + * Currently expanded menu item; must be collapsed when we clear. + */ + private MenuItemImpl mExpandedItem; + + /** + * Called by menu to notify of close and selection changes. + */ + public interface Callback { + /** + * Called when a menu item is selected. + * @param menu The menu that is the parent of the item + * @param item The menu item that is selected + * @return whether the menu item selection was handled + */ + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item); + + /** + * Called when the mode of the menu changes (for example, from icon to expanded). + * + * @param menu the menu that has changed modes + */ + public void onMenuModeChange(MenuBuilder menu); + } + + /** + * Called by menu items to execute their associated action + */ + public interface ItemInvoker { + public boolean invokeItem(MenuItemImpl item); + } + + public MenuBuilder(Context context) { + mContext = context; + mResources = context.getResources(); + + mItems = new ArrayList(); + + mVisibleItems = new ArrayList(); + mIsVisibleItemsStale = true; + + mActionItems = new ArrayList(); + mNonActionItems = new ArrayList(); + mIsActionItemsStale = true; + + setShortcutsVisibleInner(true); + } + + public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) { + mDefaultShowAsAction = defaultShowAsAction; + return this; + } + + /** + * Add a presenter to this menu. This will only hold a WeakReference; + * you do not need to explicitly remove a presenter, but you can using + * {@link #removeMenuPresenter(MenuPresenter)}. + * + * @param presenter The presenter to add + */ + public void addMenuPresenter(MenuPresenter presenter) { + mPresenters.add(new WeakReference(presenter)); + presenter.initForMenu(mContext, this); + mIsActionItemsStale = true; + } + + /** + * Remove a presenter from this menu. That presenter will no longer + * receive notifications of updates to this menu's data. + * + * @param presenter The presenter to remove + */ + public void removeMenuPresenter(MenuPresenter presenter) { + for (WeakReference ref : mPresenters) { + final MenuPresenter item = ref.get(); + if (item == null || item == presenter) { + mPresenters.remove(ref); + } + } + } + + private void dispatchPresenterUpdate(boolean cleared) { + if (mPresenters.isEmpty()) return; + + stopDispatchingItemsChanged(); + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else { + presenter.updateMenuView(cleared); + } + } + startDispatchingItemsChanged(); + } + + private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) { + if (mPresenters.isEmpty()) return false; + + boolean result = false; + + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else if (!result) { + result = presenter.onSubMenuSelected(subMenu); + } + } + return result; + } + + private void dispatchSaveInstanceState(Bundle outState) { + if (mPresenters.isEmpty()) return; + + SparseArray presenterStates = new SparseArray(); + + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else { + final int id = presenter.getId(); + if (id > 0) { + final Parcelable state = presenter.onSaveInstanceState(); + if (state != null) { + presenterStates.put(id, state); + } + } + } + } + + outState.putSparseParcelableArray(PRESENTER_KEY, presenterStates); + } + + private void dispatchRestoreInstanceState(Bundle state) { + SparseArray presenterStates = state.getSparseParcelableArray(PRESENTER_KEY); + + if (presenterStates == null || mPresenters.isEmpty()) return; + + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else { + final int id = presenter.getId(); + if (id > 0) { + Parcelable parcel = presenterStates.get(id); + if (parcel != null) { + presenter.onRestoreInstanceState(parcel); + } + } + } + } + } + + public void savePresenterStates(Bundle outState) { + dispatchSaveInstanceState(outState); + } + + public void restorePresenterStates(Bundle state) { + dispatchRestoreInstanceState(state); + } + + public void saveActionViewStates(Bundle outStates) { + SparseArray viewStates = null; + + final int itemCount = size(); + for (int i = 0; i < itemCount; i++) { + final MenuItem item = getItem(i); + final View v = item.getActionView(); + if (v != null && v.getId() != View.NO_ID) { + if (viewStates == null) { + viewStates = new SparseArray(); + } + v.saveHierarchyState(viewStates); + if (item.isActionViewExpanded()) { + outStates.putInt(EXPANDED_ACTION_VIEW_ID, item.getItemId()); + } + } + if (item.hasSubMenu()) { + final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); + subMenu.saveActionViewStates(outStates); + } + } + + if (viewStates != null) { + outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates); + } + } + + public void restoreActionViewStates(Bundle states) { + if (states == null) { + return; + } + + SparseArray viewStates = states.getSparseParcelableArray( + getActionViewStatesKey()); + + final int itemCount = size(); + for (int i = 0; i < itemCount; i++) { + final MenuItem item = getItem(i); + final View v = item.getActionView(); + if (v != null && v.getId() != View.NO_ID) { + v.restoreHierarchyState(viewStates); + } + if (item.hasSubMenu()) { + final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); + subMenu.restoreActionViewStates(states); + } + } + + final int expandedId = states.getInt(EXPANDED_ACTION_VIEW_ID); + if (expandedId > 0) { + MenuItem itemToExpand = findItem(expandedId); + if (itemToExpand != null) { + itemToExpand.expandActionView(); + } + } + } + + protected String getActionViewStatesKey() { + return ACTION_VIEW_STATES_KEY; + } + + public void setCallback(Callback cb) { + mCallback = cb; + } + + /** + * Adds an item to the menu. The other add methods funnel to this. + */ + private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) { + final int ordering = getOrdering(categoryOrder); + + final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, + ordering, title, mDefaultShowAsAction); + + if (mCurrentMenuInfo != null) { + // Pass along the current menu info + item.setMenuInfo(mCurrentMenuInfo); + } + + mItems.add(findInsertIndex(mItems, ordering), item); + onItemsChanged(true); + + return item; + } + + public MenuItem add(CharSequence title) { + return addInternal(0, 0, 0, title); + } + + public MenuItem add(int titleRes) { + return addInternal(0, 0, 0, mResources.getString(titleRes)); + } + + public MenuItem add(int group, int id, int categoryOrder, CharSequence title) { + return addInternal(group, id, categoryOrder, title); + } + + public MenuItem add(int group, int id, int categoryOrder, int title) { + return addInternal(group, id, categoryOrder, mResources.getString(title)); + } + + public SubMenu addSubMenu(CharSequence title) { + return addSubMenu(0, 0, 0, title); + } + + public SubMenu addSubMenu(int titleRes) { + return addSubMenu(0, 0, 0, mResources.getString(titleRes)); + } + + public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) { + final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title); + final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item); + item.setSubMenu(subMenu); + + return subMenu; + } + + public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) { + return addSubMenu(group, id, categoryOrder, mResources.getString(title)); + } + + public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller, + Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) { + PackageManager pm = mContext.getPackageManager(); + final List lri = + pm.queryIntentActivityOptions(caller, specifics, intent, 0); + final int N = lri != null ? lri.size() : 0; + + if ((flags & FLAG_APPEND_TO_GROUP) == 0) { + removeGroup(group); + } + + for (int i=0; i= 0) { + outSpecificItems[ri.specificIndex] = item; + } + } + + return N; + } + + public void removeItem(int id) { + removeItemAtInt(findItemIndex(id), true); + } + + public void removeGroup(int group) { + final int i = findGroupIndex(group); + + if (i >= 0) { + final int maxRemovable = mItems.size() - i; + int numRemoved = 0; + while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) { + // Don't force update for each one, this method will do it at the end + removeItemAtInt(i, false); + } + + // Notify menu views + onItemsChanged(true); + } + } + + /** + * Remove the item at the given index and optionally forces menu views to + * update. + * + * @param index The index of the item to be removed. If this index is + * invalid an exception is thrown. + * @param updateChildrenOnMenuViews Whether to force update on menu views. + * Please make sure you eventually call this after your batch of + * removals. + */ + private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) { + if ((index < 0) || (index >= mItems.size())) return; + + mItems.remove(index); + + if (updateChildrenOnMenuViews) onItemsChanged(true); + } + + public void removeItemAt(int index) { + removeItemAtInt(index, true); + } + + public void clearAll() { + mPreventDispatchingItemsChanged = true; + clear(); + clearHeader(); + mPreventDispatchingItemsChanged = false; + mItemsChangedWhileDispatchPrevented = false; + onItemsChanged(true); + } + + public void clear() { + if (mExpandedItem != null) { + collapseItemActionView(mExpandedItem); + } + mItems.clear(); + + onItemsChanged(true); + } + + void setExclusiveItemChecked(MenuItem item) { + final int group = item.getGroupId(); + + final int N = mItems.size(); + for (int i = 0; i < N; i++) { + MenuItemImpl curItem = mItems.get(i); + if (curItem.getGroupId() == group) { + if (!curItem.isExclusiveCheckable()) continue; + if (!curItem.isCheckable()) continue; + + // Check the item meant to be checked, uncheck the others (that are in the group) + curItem.setCheckedInt(curItem == item); + } + } + } + + public void setGroupCheckable(int group, boolean checkable, boolean exclusive) { + final int N = mItems.size(); + + for (int i = 0; i < N; i++) { + MenuItemImpl item = mItems.get(i); + if (item.getGroupId() == group) { + item.setExclusiveCheckable(exclusive); + item.setCheckable(checkable); + } + } + } + + public void setGroupVisible(int group, boolean visible) { + final int N = mItems.size(); + + // We handle the notification of items being changed ourselves, so we use setVisibleInt rather + // than setVisible and at the end notify of items being changed + + boolean changedAtLeastOneItem = false; + for (int i = 0; i < N; i++) { + MenuItemImpl item = mItems.get(i); + if (item.getGroupId() == group) { + if (item.setVisibleInt(visible)) changedAtLeastOneItem = true; + } + } + + if (changedAtLeastOneItem) onItemsChanged(true); + } + + public void setGroupEnabled(int group, boolean enabled) { + final int N = mItems.size(); + + for (int i = 0; i < N; i++) { + MenuItemImpl item = mItems.get(i); + if (item.getGroupId() == group) { + item.setEnabled(enabled); + } + } + } + + public boolean hasVisibleItems() { + final int size = size(); + + for (int i = 0; i < size; i++) { + MenuItemImpl item = mItems.get(i); + if (item.isVisible()) { + return true; + } + } + + return false; + } + + public MenuItem findItem(int id) { + final int size = size(); + for (int i = 0; i < size; i++) { + MenuItemImpl item = mItems.get(i); + if (item.getItemId() == id) { + return item; + } else if (item.hasSubMenu()) { + MenuItem possibleItem = item.getSubMenu().findItem(id); + + if (possibleItem != null) { + return possibleItem; + } + } + } + + return null; + } + + public int findItemIndex(int id) { + final int size = size(); + + for (int i = 0; i < size; i++) { + MenuItemImpl item = mItems.get(i); + if (item.getItemId() == id) { + return i; + } + } + + return -1; + } + + public int findGroupIndex(int group) { + return findGroupIndex(group, 0); + } + + public int findGroupIndex(int group, int start) { + final int size = size(); + + if (start < 0) { + start = 0; + } + + for (int i = start; i < size; i++) { + final MenuItemImpl item = mItems.get(i); + + if (item.getGroupId() == group) { + return i; + } + } + + return -1; + } + + public int size() { + return mItems.size(); + } + + /** {@inheritDoc} */ + public MenuItem getItem(int index) { + return mItems.get(index); + } + + public boolean isShortcutKey(int keyCode, KeyEvent event) { + return findItemWithShortcutForKey(keyCode, event) != null; + } + + public void setQwertyMode(boolean isQwerty) { + mQwertyMode = isQwerty; + + onItemsChanged(false); + } + + /** + * Returns the ordering across all items. This will grab the category from + * the upper bits, find out how to order the category with respect to other + * categories, and combine it with the lower bits. + * + * @param categoryOrder The category order for a particular item (if it has + * not been or/add with a category, the default category is + * assumed). + * @return An ordering integer that can be used to order this item across + * all the items (even from other categories). + */ + private static int getOrdering(int categoryOrder) { + final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT; + + if (index < 0 || index >= sCategoryToOrder.length) { + throw new IllegalArgumentException("order does not contain a valid category."); + } + + return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK); + } + + /** + * @return whether the menu shortcuts are in qwerty mode or not + */ + boolean isQwertyMode() { + return mQwertyMode; + } + + /** + * Sets whether the shortcuts should be visible on menus. Devices without hardware + * key input will never make shortcuts visible even if this method is passed 'true'. + * + * @param shortcutsVisible Whether shortcuts should be visible (if true and a + * menu item does not have a shortcut defined, that item will + * still NOT show a shortcut) + */ + public void setShortcutsVisible(boolean shortcutsVisible) { + if (mShortcutsVisible == shortcutsVisible) return; + + setShortcutsVisibleInner(shortcutsVisible); + onItemsChanged(false); + } + + private void setShortcutsVisibleInner(boolean shortcutsVisible) { + mShortcutsVisible = shortcutsVisible + && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS + && mResources.getBoolean( + R.bool.abs__config_showMenuShortcutsWhenKeyboardPresent); + } + + /** + * @return Whether shortcuts should be visible on menus. + */ + public boolean isShortcutsVisible() { + return mShortcutsVisible; + } + + Resources getResources() { + return mResources; + } + + public Context getContext() { + return mContext; + } + + boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) { + return mCallback != null && mCallback.onMenuItemSelected(menu, item); + } + + /** + * Dispatch a mode change event to this menu's callback. + */ + public void changeMenuMode() { + if (mCallback != null) { + mCallback.onMenuModeChange(this); + } + } + + private static int findInsertIndex(ArrayList items, int ordering) { + for (int i = items.size() - 1; i >= 0; i--) { + MenuItemImpl item = items.get(i); + if (item.getOrdering() <= ordering) { + return i + 1; + } + } + + return 0; + } + + public boolean performShortcut(int keyCode, KeyEvent event, int flags) { + final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event); + + boolean handled = false; + + if (item != null) { + handled = performItemAction(item, flags); + } + + if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) { + close(true); + } + + return handled; + } + + /* + * This function will return all the menu and sub-menu items that can + * be directly (the shortcut directly corresponds) and indirectly + * (the ALT-enabled char corresponds to the shortcut) associated + * with the keyCode. + */ + @SuppressWarnings("deprecation") + void findItemsWithShortcutForKey(List items, int keyCode, KeyEvent event) { + final boolean qwerty = isQwertyMode(); + final int metaState = event.getMetaState(); + final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData(); + // Get the chars associated with the keyCode (i.e using any chording combo) + final boolean isKeyCodeMapped = event.getKeyData(possibleChars); + // The delete key is not mapped to '\b' so we treat it specially + if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) { + return; + } + + // Look for an item whose shortcut is this key. + final int N = mItems.size(); + for (int i = 0; i < N; i++) { + MenuItemImpl item = mItems.get(i); + if (item.hasSubMenu()) { + ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event); + } + final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut(); + if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) && + (shortcutChar != 0) && + (shortcutChar == possibleChars.meta[0] + || shortcutChar == possibleChars.meta[2] + || (qwerty && shortcutChar == '\b' && + keyCode == KeyEvent.KEYCODE_DEL)) && + item.isEnabled()) { + items.add(item); + } + } + } + + /* + * We want to return the menu item associated with the key, but if there is no + * ambiguity (i.e. there is only one menu item corresponding to the key) we want + * to return it even if it's not an exact match; this allow the user to + * _not_ use the ALT key for example, making the use of shortcuts slightly more + * user-friendly. An example is on the G1, '!' and '1' are on the same key, and + * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut). + * + * On the other hand, if two (or more) shortcuts corresponds to the same key, + * we have to only return the exact match. + */ + @SuppressWarnings("deprecation") + MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) { + // Get all items that can be associated directly or indirectly with the keyCode + ArrayList items = mTempShortcutItemList; + items.clear(); + findItemsWithShortcutForKey(items, keyCode, event); + + if (items.isEmpty()) { + return null; + } + + final int metaState = event.getMetaState(); + final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData(); + // Get the chars associated with the keyCode (i.e using any chording combo) + event.getKeyData(possibleChars); + + // If we have only one element, we can safely returns it + final int size = items.size(); + if (size == 1) { + return items.get(0); + } + + final boolean qwerty = isQwertyMode(); + // If we found more than one item associated with the key, + // we have to return the exact match + for (int i = 0; i < size; i++) { + final MenuItemImpl item = items.get(i); + final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : + item.getNumericShortcut(); + if ((shortcutChar == possibleChars.meta[0] && + (metaState & KeyEvent.META_ALT_ON) == 0) + || (shortcutChar == possibleChars.meta[2] && + (metaState & KeyEvent.META_ALT_ON) != 0) + || (qwerty && shortcutChar == '\b' && + keyCode == KeyEvent.KEYCODE_DEL)) { + return item; + } + } + return null; + } + + public boolean performIdentifierAction(int id, int flags) { + // Look for an item whose identifier is the id. + return performItemAction(findItem(id), flags); + } + + public boolean performItemAction(MenuItem item, int flags) { + MenuItemImpl itemImpl = (MenuItemImpl) item; + + if (itemImpl == null || !itemImpl.isEnabled()) { + return false; + } + + boolean invoked = itemImpl.invoke(); + + if (itemImpl.hasCollapsibleActionView()) { + invoked |= itemImpl.expandActionView(); + if (invoked) close(true); + } else if (item.hasSubMenu()) { + close(false); + + final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); + final ActionProvider provider = item.getActionProvider(); + if (provider != null && provider.hasSubMenu()) { + provider.onPrepareSubMenu(subMenu); + } + invoked |= dispatchSubMenuSelected(subMenu); + if (!invoked) close(true); + } else { + if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) { + close(true); + } + } + + return invoked; + } + + /** + * Closes the visible menu. + * + * @param allMenusAreClosing Whether the menus are completely closing (true), + * or whether there is another menu coming in this menu's place + * (false). For example, if the menu is closing because a + * sub menu is about to be shown, allMenusAreClosing + * is false. + */ + final void close(boolean allMenusAreClosing) { + if (mIsClosing) return; + + mIsClosing = true; + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else { + presenter.onCloseMenu(this, allMenusAreClosing); + } + } + mIsClosing = false; + } + + /** {@inheritDoc} */ + public void close() { + close(true); + } + + /** + * Called when an item is added or removed. + * + * @param structureChanged true if the menu structure changed, + * false if only item properties changed. + * (Visibility is a structural property since it affects layout.) + */ + void onItemsChanged(boolean structureChanged) { + if (!mPreventDispatchingItemsChanged) { + if (structureChanged) { + mIsVisibleItemsStale = true; + mIsActionItemsStale = true; + } + + dispatchPresenterUpdate(structureChanged); + } else { + mItemsChangedWhileDispatchPrevented = true; + } + } + + /** + * Stop dispatching item changed events to presenters until + * {@link #startDispatchingItemsChanged()} is called. Useful when + * many menu operations are going to be performed as a batch. + */ + public void stopDispatchingItemsChanged() { + if (!mPreventDispatchingItemsChanged) { + mPreventDispatchingItemsChanged = true; + mItemsChangedWhileDispatchPrevented = false; + } + } + + public void startDispatchingItemsChanged() { + mPreventDispatchingItemsChanged = false; + + if (mItemsChangedWhileDispatchPrevented) { + mItemsChangedWhileDispatchPrevented = false; + onItemsChanged(true); + } + } + + /** + * Called by {@link MenuItemImpl} when its visible flag is changed. + * @param item The item that has gone through a visibility change. + */ + void onItemVisibleChanged(MenuItemImpl item) { + // Notify of items being changed + mIsVisibleItemsStale = true; + onItemsChanged(true); + } + + /** + * Called by {@link MenuItemImpl} when its action request status is changed. + * @param item The item that has gone through a change in action request status. + */ + void onItemActionRequestChanged(MenuItemImpl item) { + // Notify of items being changed + mIsActionItemsStale = true; + onItemsChanged(true); + } + + ArrayList getVisibleItems() { + if (!mIsVisibleItemsStale) return mVisibleItems; + + // Refresh the visible items + mVisibleItems.clear(); + + final int itemsSize = mItems.size(); + MenuItemImpl item; + for (int i = 0; i < itemsSize; i++) { + item = mItems.get(i); + if (item.isVisible()) mVisibleItems.add(item); + } + + mIsVisibleItemsStale = false; + mIsActionItemsStale = true; + + return mVisibleItems; + } + + /** + * This method determines which menu items get to be 'action items' that will appear + * in an action bar and which items should be 'overflow items' in a secondary menu. + * The rules are as follows: + * + *

Items are considered for inclusion in the order specified within the menu. + * There is a limit of mMaxActionItems as a total count, optionally including the overflow + * menu button itself. This is a soft limit; if an item shares a group ID with an item + * previously included as an action item, the new item will stay with its group and become + * an action item itself even if it breaks the max item count limit. This is done to + * limit the conceptual complexity of the items presented within an action bar. Only a few + * unrelated concepts should be presented to the user in this space, and groups are treated + * as a single concept. + * + *

There is also a hard limit of consumed measurable space: mActionWidthLimit. This + * limit may be broken by a single item that exceeds the remaining space, but no further + * items may be added. If an item that is part of a group cannot fit within the remaining + * measured width, the entire group will be demoted to overflow. This is done to ensure room + * for navigation and other affordances in the action bar as well as reduce general UI clutter. + * + *

The space freed by demoting a full group cannot be consumed by future menu items. + * Once items begin to overflow, all future items become overflow items as well. This is + * to avoid inadvertent reordering that may break the app's intended design. + */ + public void flagActionItems() { + if (!mIsActionItemsStale) { + return; + } + + // Presenters flag action items as needed. + boolean flagged = false; + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else { + flagged |= presenter.flagActionItems(); + } + } + + if (flagged) { + mActionItems.clear(); + mNonActionItems.clear(); + ArrayList visibleItems = getVisibleItems(); + final int itemsSize = visibleItems.size(); + for (int i = 0; i < itemsSize; i++) { + MenuItemImpl item = visibleItems.get(i); + if (item.isActionButton()) { + mActionItems.add(item); + } else { + mNonActionItems.add(item); + } + } + } else { + // Nobody flagged anything, everything is a non-action item. + // (This happens during a first pass with no action-item presenters.) + mActionItems.clear(); + mNonActionItems.clear(); + mNonActionItems.addAll(getVisibleItems()); + } + mIsActionItemsStale = false; + } + + ArrayList getActionItems() { + flagActionItems(); + return mActionItems; + } + + ArrayList getNonActionItems() { + flagActionItems(); + return mNonActionItems; + } + + public void clearHeader() { + mHeaderIcon = null; + mHeaderTitle = null; + mHeaderView = null; + + onItemsChanged(false); + } + + private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes, + final Drawable icon, final View view) { + final Resources r = getResources(); + + if (view != null) { + mHeaderView = view; + + // If using a custom view, then the title and icon aren't used + mHeaderTitle = null; + mHeaderIcon = null; + } else { + if (titleRes > 0) { + mHeaderTitle = r.getText(titleRes); + } else if (title != null) { + mHeaderTitle = title; + } + + if (iconRes > 0) { + mHeaderIcon = r.getDrawable(iconRes); + } else if (icon != null) { + mHeaderIcon = icon; + } + + // If using the title or icon, then a custom view isn't used + mHeaderView = null; + } + + // Notify of change + onItemsChanged(false); + } + + /** + * Sets the header's title. This replaces the header view. Called by the + * builder-style methods of subclasses. + * + * @param title The new title. + * @return This MenuBuilder so additional setters can be called. + */ + protected MenuBuilder setHeaderTitleInt(CharSequence title) { + setHeaderInternal(0, title, 0, null, null); + return this; + } + + /** + * Sets the header's title. This replaces the header view. Called by the + * builder-style methods of subclasses. + * + * @param titleRes The new title (as a resource ID). + * @return This MenuBuilder so additional setters can be called. + */ + protected MenuBuilder setHeaderTitleInt(int titleRes) { + setHeaderInternal(titleRes, null, 0, null, null); + return this; + } + + /** + * Sets the header's icon. This replaces the header view. Called by the + * builder-style methods of subclasses. + * + * @param icon The new icon. + * @return This MenuBuilder so additional setters can be called. + */ + protected MenuBuilder setHeaderIconInt(Drawable icon) { + setHeaderInternal(0, null, 0, icon, null); + return this; + } + + /** + * Sets the header's icon. This replaces the header view. Called by the + * builder-style methods of subclasses. + * + * @param iconRes The new icon (as a resource ID). + * @return This MenuBuilder so additional setters can be called. + */ + protected MenuBuilder setHeaderIconInt(int iconRes) { + setHeaderInternal(0, null, iconRes, null, null); + return this; + } + + /** + * Sets the header's view. This replaces the title and icon. Called by the + * builder-style methods of subclasses. + * + * @param view The new view. + * @return This MenuBuilder so additional setters can be called. + */ + protected MenuBuilder setHeaderViewInt(View view) { + setHeaderInternal(0, null, 0, null, view); + return this; + } + + public CharSequence getHeaderTitle() { + return mHeaderTitle; + } + + public Drawable getHeaderIcon() { + return mHeaderIcon; + } + + public View getHeaderView() { + return mHeaderView; + } + + /** + * Gets the root menu (if this is a submenu, find its root menu). + * @return The root menu. + */ + public MenuBuilder getRootMenu() { + return this; + } + + /** + * Sets the current menu info that is set on all items added to this menu + * (until this is called again with different menu info, in which case that + * one will be added to all subsequent item additions). + * + * @param menuInfo The extra menu information to add. + */ + public void setCurrentMenuInfo(ContextMenuInfo menuInfo) { + mCurrentMenuInfo = menuInfo; + } + + void setOptionalIconsVisible(boolean visible) { + mOptionalIconsVisible = visible; + } + + boolean getOptionalIconsVisible() { + return mOptionalIconsVisible; + } + + public boolean expandItemActionView(MenuItemImpl item) { + if (mPresenters.isEmpty()) return false; + + boolean expanded = false; + + stopDispatchingItemsChanged(); + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else if ((expanded = presenter.expandItemActionView(this, item))) { + break; + } + } + startDispatchingItemsChanged(); + + if (expanded) { + mExpandedItem = item; + } + return expanded; + } + + public boolean collapseItemActionView(MenuItemImpl item) { + if (mPresenters.isEmpty() || mExpandedItem != item) return false; + + boolean collapsed = false; + + stopDispatchingItemsChanged(); + for (WeakReference ref : mPresenters) { + final MenuPresenter presenter = ref.get(); + if (presenter == null) { + mPresenters.remove(ref); + } else if ((collapsed = presenter.collapseItemActionView(this, item))) { + break; + } + } + startDispatchingItemsChanged(); + + if (collapsed) { + mExpandedItem = null; + } + return collapsed; + } + + public MenuItemImpl getExpandedItem() { + return mExpandedItem; + } + + public boolean bindNativeOverflow(android.view.Menu menu, android.view.MenuItem.OnMenuItemClickListener listener, HashMap map) { + final List nonActionItems = getNonActionItems(); + if (nonActionItems == null || nonActionItems.size() == 0) { + return false; + } + + boolean visible = false; + menu.clear(); + for (MenuItemImpl nonActionItem : nonActionItems) { + if (!nonActionItem.isVisible()) { + continue; + } + visible = true; + + android.view.MenuItem nativeItem; + if (nonActionItem.hasSubMenu()) { + android.view.SubMenu nativeSub = menu.addSubMenu(nonActionItem.getGroupId(), nonActionItem.getItemId(), + nonActionItem.getOrder(), nonActionItem.getTitle()); + + SubMenuBuilder subMenu = (SubMenuBuilder)nonActionItem.getSubMenu(); + for (MenuItemImpl subItem : subMenu.getVisibleItems()) { + android.view.MenuItem nativeSubItem = nativeSub.add(subItem.getGroupId(), subItem.getItemId(), + subItem.getOrder(), subItem.getTitle()); + + nativeSubItem.setIcon(subItem.getIcon()); + nativeSubItem.setOnMenuItemClickListener(listener); + nativeSubItem.setEnabled(subItem.isEnabled()); + nativeSubItem.setIntent(subItem.getIntent()); + nativeSubItem.setNumericShortcut(subItem.getNumericShortcut()); + nativeSubItem.setAlphabeticShortcut(subItem.getAlphabeticShortcut()); + nativeSubItem.setTitleCondensed(subItem.getTitleCondensed()); + nativeSubItem.setCheckable(subItem.isCheckable()); + nativeSubItem.setChecked(subItem.isChecked()); + + if (subItem.isExclusiveCheckable()) { + nativeSub.setGroupCheckable(subItem.getGroupId(), true, true); + } + + map.put(nativeSubItem, subItem); + } + + nativeItem = nativeSub.getItem(); + } else { + nativeItem = menu.add(nonActionItem.getGroupId(), nonActionItem.getItemId(), + nonActionItem.getOrder(), nonActionItem.getTitle()); + } + nativeItem.setIcon(nonActionItem.getIcon()); + nativeItem.setOnMenuItemClickListener(listener); + nativeItem.setEnabled(nonActionItem.isEnabled()); + nativeItem.setIntent(nonActionItem.getIntent()); + nativeItem.setNumericShortcut(nonActionItem.getNumericShortcut()); + nativeItem.setAlphabeticShortcut(nonActionItem.getAlphabeticShortcut()); + nativeItem.setTitleCondensed(nonActionItem.getTitleCondensed()); + nativeItem.setCheckable(nonActionItem.isCheckable()); + nativeItem.setChecked(nonActionItem.isChecked()); + + if (nonActionItem.isExclusiveCheckable()) { + menu.setGroupCheckable(nonActionItem.getGroupId(), true, true); + } + + map.put(nativeItem, nonActionItem); + } + return visible; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java new file mode 100644 index 000000000..f5359fb40 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewDebug; +import android.widget.LinearLayout; + +import com.actionbarsherlock.view.ActionProvider; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +/** + * @hide + */ +public final class MenuItemImpl implements MenuItem { + private static final String TAG = "MenuItemImpl"; + + private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER | + SHOW_AS_ACTION_IF_ROOM | + SHOW_AS_ACTION_ALWAYS; + + private final int mId; + private final int mGroup; + private final int mCategoryOrder; + private final int mOrdering; + private CharSequence mTitle; + private CharSequence mTitleCondensed; + private Intent mIntent; + private char mShortcutNumericChar; + private char mShortcutAlphabeticChar; + + /** The icon's drawable which is only created as needed */ + private Drawable mIconDrawable; + /** + * The icon's resource ID which is used to get the Drawable when it is + * needed (if the Drawable isn't already obtained--only one of the two is + * needed). + */ + private int mIconResId = NO_ICON; + + /** The menu to which this item belongs */ + private MenuBuilder mMenu; + /** If this item should launch a sub menu, this is the sub menu to launch */ + private SubMenuBuilder mSubMenu; + + private Runnable mItemCallback; + private MenuItem.OnMenuItemClickListener mClickListener; + + private int mFlags = ENABLED; + private static final int CHECKABLE = 0x00000001; + private static final int CHECKED = 0x00000002; + private static final int EXCLUSIVE = 0x00000004; + private static final int HIDDEN = 0x00000008; + private static final int ENABLED = 0x00000010; + private static final int IS_ACTION = 0x00000020; + + private int mShowAsAction = SHOW_AS_ACTION_NEVER; + + private View mActionView; + private ActionProvider mActionProvider; + private OnActionExpandListener mOnActionExpandListener; + private boolean mIsActionViewExpanded = false; + + /** Used for the icon resource ID if this item does not have an icon */ + static final int NO_ICON = 0; + + /** + * Current use case is for context menu: Extra information linked to the + * View that added this item to the context menu. + */ + private ContextMenuInfo mMenuInfo; + + private static String sPrependShortcutLabel; + private static String sEnterShortcutLabel; + private static String sDeleteShortcutLabel; + private static String sSpaceShortcutLabel; + + + /** + * Instantiates this menu item. + * + * @param menu + * @param group Item ordering grouping control. The item will be added after + * all other items whose order is <= this number, and before any + * that are larger than it. This can also be used to define + * groups of items for batch state changes. Normally use 0. + * @param id Unique item ID. Use 0 if you do not need a unique ID. + * @param categoryOrder The ordering for this item. + * @param title The text to display for the item. + */ + MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, + CharSequence title, int showAsAction) { + + /* TODO if (sPrependShortcutLabel == null) { + // This is instantiated from the UI thread, so no chance of sync issues + sPrependShortcutLabel = menu.getContext().getResources().getString( + com.android.internal.R.string.prepend_shortcut_label); + sEnterShortcutLabel = menu.getContext().getResources().getString( + com.android.internal.R.string.menu_enter_shortcut_label); + sDeleteShortcutLabel = menu.getContext().getResources().getString( + com.android.internal.R.string.menu_delete_shortcut_label); + sSpaceShortcutLabel = menu.getContext().getResources().getString( + com.android.internal.R.string.menu_space_shortcut_label); + }*/ + + mMenu = menu; + mId = id; + mGroup = group; + mCategoryOrder = categoryOrder; + mOrdering = ordering; + mTitle = title; + mShowAsAction = showAsAction; + } + + /** + * Invokes the item by calling various listeners or callbacks. + * + * @return true if the invocation was handled, false otherwise + */ + public boolean invoke() { + if (mClickListener != null && + mClickListener.onMenuItemClick(this)) { + return true; + } + + if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) { + return true; + } + + if (mItemCallback != null) { + mItemCallback.run(); + return true; + } + + if (mIntent != null) { + try { + mMenu.getContext().startActivity(mIntent); + return true; + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Can't find activity to handle intent; ignoring", e); + } + } + + if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) { + return true; + } + + return false; + } + + public boolean isEnabled() { + return (mFlags & ENABLED) != 0; + } + + public MenuItem setEnabled(boolean enabled) { + if (enabled) { + mFlags |= ENABLED; + } else { + mFlags &= ~ENABLED; + } + + mMenu.onItemsChanged(false); + + return this; + } + + public int getGroupId() { + return mGroup; + } + + @ViewDebug.CapturedViewProperty + public int getItemId() { + return mId; + } + + public int getOrder() { + return mCategoryOrder; + } + + public int getOrdering() { + return mOrdering; + } + + public Intent getIntent() { + return mIntent; + } + + public MenuItem setIntent(Intent intent) { + mIntent = intent; + return this; + } + + Runnable getCallback() { + return mItemCallback; + } + + public MenuItem setCallback(Runnable callback) { + mItemCallback = callback; + return this; + } + + public char getAlphabeticShortcut() { + return mShortcutAlphabeticChar; + } + + public MenuItem setAlphabeticShortcut(char alphaChar) { + if (mShortcutAlphabeticChar == alphaChar) return this; + + mShortcutAlphabeticChar = Character.toLowerCase(alphaChar); + + mMenu.onItemsChanged(false); + + return this; + } + + public char getNumericShortcut() { + return mShortcutNumericChar; + } + + public MenuItem setNumericShortcut(char numericChar) { + if (mShortcutNumericChar == numericChar) return this; + + mShortcutNumericChar = numericChar; + + mMenu.onItemsChanged(false); + + return this; + } + + public MenuItem setShortcut(char numericChar, char alphaChar) { + mShortcutNumericChar = numericChar; + mShortcutAlphabeticChar = Character.toLowerCase(alphaChar); + + mMenu.onItemsChanged(false); + + return this; + } + + /** + * @return The active shortcut (based on QWERTY-mode of the menu). + */ + char getShortcut() { + return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar); + } + + /** + * @return The label to show for the shortcut. This includes the chording + * key (for example 'Menu+a'). Also, any non-human readable + * characters should be human readable (for example 'Menu+enter'). + */ + String getShortcutLabel() { + + char shortcut = getShortcut(); + if (shortcut == 0) { + return ""; + } + + StringBuilder sb = new StringBuilder(sPrependShortcutLabel); + switch (shortcut) { + + case '\n': + sb.append(sEnterShortcutLabel); + break; + + case '\b': + sb.append(sDeleteShortcutLabel); + break; + + case ' ': + sb.append(sSpaceShortcutLabel); + break; + + default: + sb.append(shortcut); + break; + } + + return sb.toString(); + } + + /** + * @return Whether this menu item should be showing shortcuts (depends on + * whether the menu should show shortcuts and whether this item has + * a shortcut defined) + */ + boolean shouldShowShortcut() { + // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut + return mMenu.isShortcutsVisible() && (getShortcut() != 0); + } + + public SubMenu getSubMenu() { + return mSubMenu; + } + + public boolean hasSubMenu() { + return mSubMenu != null; + } + + void setSubMenu(SubMenuBuilder subMenu) { + mSubMenu = subMenu; + + subMenu.setHeaderTitle(getTitle()); + } + + @ViewDebug.CapturedViewProperty + public CharSequence getTitle() { + return mTitle; + } + + /** + * Gets the title for a particular {@link ItemView} + * + * @param itemView The ItemView that is receiving the title + * @return Either the title or condensed title based on what the ItemView + * prefers + */ + CharSequence getTitleForItemView(MenuView.ItemView itemView) { + return ((itemView != null) && itemView.prefersCondensedTitle()) + ? getTitleCondensed() + : getTitle(); + } + + public MenuItem setTitle(CharSequence title) { + mTitle = title; + + mMenu.onItemsChanged(false); + + if (mSubMenu != null) { + mSubMenu.setHeaderTitle(title); + } + + return this; + } + + public MenuItem setTitle(int title) { + return setTitle(mMenu.getContext().getString(title)); + } + + public CharSequence getTitleCondensed() { + return mTitleCondensed != null ? mTitleCondensed : mTitle; + } + + public MenuItem setTitleCondensed(CharSequence title) { + mTitleCondensed = title; + + // Could use getTitle() in the loop below, but just cache what it would do here + if (title == null) { + title = mTitle; + } + + mMenu.onItemsChanged(false); + + return this; + } + + public Drawable getIcon() { + if (mIconDrawable != null) { + return mIconDrawable; + } + + if (mIconResId != NO_ICON) { + return mMenu.getResources().getDrawable(mIconResId); + } + + return null; + } + + public MenuItem setIcon(Drawable icon) { + mIconResId = NO_ICON; + mIconDrawable = icon; + mMenu.onItemsChanged(false); + + return this; + } + + public MenuItem setIcon(int iconResId) { + mIconDrawable = null; + mIconResId = iconResId; + + // If we have a view, we need to push the Drawable to them + mMenu.onItemsChanged(false); + + return this; + } + + public boolean isCheckable() { + return (mFlags & CHECKABLE) == CHECKABLE; + } + + public MenuItem setCheckable(boolean checkable) { + final int oldFlags = mFlags; + mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0); + if (oldFlags != mFlags) { + mMenu.onItemsChanged(false); + } + + return this; + } + + public void setExclusiveCheckable(boolean exclusive) { + mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0); + } + + public boolean isExclusiveCheckable() { + return (mFlags & EXCLUSIVE) != 0; + } + + public boolean isChecked() { + return (mFlags & CHECKED) == CHECKED; + } + + public MenuItem setChecked(boolean checked) { + if ((mFlags & EXCLUSIVE) != 0) { + // Call the method on the Menu since it knows about the others in this + // exclusive checkable group + mMenu.setExclusiveItemChecked(this); + } else { + setCheckedInt(checked); + } + + return this; + } + + void setCheckedInt(boolean checked) { + final int oldFlags = mFlags; + mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0); + if (oldFlags != mFlags) { + mMenu.onItemsChanged(false); + } + } + + public boolean isVisible() { + return (mFlags & HIDDEN) == 0; + } + + /** + * Changes the visibility of the item. This method DOES NOT notify the + * parent menu of a change in this item, so this should only be called from + * methods that will eventually trigger this change. If unsure, use {@link #setVisible(boolean)} + * instead. + * + * @param shown Whether to show (true) or hide (false). + * @return Whether the item's shown state was changed + */ + boolean setVisibleInt(boolean shown) { + final int oldFlags = mFlags; + mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN); + return oldFlags != mFlags; + } + + public MenuItem setVisible(boolean shown) { + // Try to set the shown state to the given state. If the shown state was changed + // (i.e. the previous state isn't the same as given state), notify the parent menu that + // the shown state has changed for this item + if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this); + + return this; + } + + public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) { + mClickListener = clickListener; + return this; + } + + @Override + public String toString() { + return mTitle.toString(); + } + + void setMenuInfo(ContextMenuInfo menuInfo) { + mMenuInfo = menuInfo; + } + + public ContextMenuInfo getMenuInfo() { + return mMenuInfo; + } + + public void actionFormatChanged() { + mMenu.onItemActionRequestChanged(this); + } + + /** + * @return Whether the menu should show icons for menu items. + */ + public boolean shouldShowIcon() { + return mMenu.getOptionalIconsVisible(); + } + + public boolean isActionButton() { + return (mFlags & IS_ACTION) == IS_ACTION; + } + + public boolean requestsActionButton() { + return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM; + } + + public boolean requiresActionButton() { + return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS; + } + + public void setIsActionButton(boolean isActionButton) { + if (isActionButton) { + mFlags |= IS_ACTION; + } else { + mFlags &= ~IS_ACTION; + } + } + + public boolean showsTextAsAction() { + return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT; + } + + public void setShowAsAction(int actionEnum) { + switch (actionEnum & SHOW_AS_ACTION_MASK) { + case SHOW_AS_ACTION_ALWAYS: + case SHOW_AS_ACTION_IF_ROOM: + case SHOW_AS_ACTION_NEVER: + // Looks good! + break; + + default: + // Mutually exclusive options selected! + throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM," + + " and SHOW_AS_ACTION_NEVER are mutually exclusive."); + } + mShowAsAction = actionEnum; + mMenu.onItemActionRequestChanged(this); + } + + public MenuItem setActionView(View view) { + mActionView = view; + mActionProvider = null; + if (view != null && view.getId() == View.NO_ID && mId > 0) { + view.setId(mId); + } + mMenu.onItemActionRequestChanged(this); + return this; + } + + public MenuItem setActionView(int resId) { + final Context context = mMenu.getContext(); + final LayoutInflater inflater = LayoutInflater.from(context); + setActionView(inflater.inflate(resId, new LinearLayout(context), false)); + return this; + } + + public View getActionView() { + if (mActionView != null) { + return mActionView; + } else if (mActionProvider != null) { + mActionView = mActionProvider.onCreateActionView(); + return mActionView; + } else { + return null; + } + } + + public ActionProvider getActionProvider() { + return mActionProvider; + } + + public MenuItem setActionProvider(ActionProvider actionProvider) { + mActionView = null; + mActionProvider = actionProvider; + mMenu.onItemsChanged(true); // Measurement can be changed + return this; + } + + @Override + public MenuItem setShowAsActionFlags(int actionEnum) { + setShowAsAction(actionEnum); + return this; + } + + @Override + public boolean expandActionView() { + if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) { + return false; + } + + if (mOnActionExpandListener == null || + mOnActionExpandListener.onMenuItemActionExpand(this)) { + return mMenu.expandItemActionView(this); + } + + return false; + } + + @Override + public boolean collapseActionView() { + if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) { + return false; + } + if (mActionView == null) { + // We're already collapsed if we have no action view. + return true; + } + + if (mOnActionExpandListener == null || + mOnActionExpandListener.onMenuItemActionCollapse(this)) { + return mMenu.collapseItemActionView(this); + } + + return false; + } + + @Override + public MenuItem setOnActionExpandListener(OnActionExpandListener listener) { + mOnActionExpandListener = listener; + return this; + } + + public boolean hasCollapsibleActionView() { + return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null; + } + + public void setActionViewExpanded(boolean isExpanded) { + mIsActionViewExpanded = isExpanded; + mMenu.onItemsChanged(false); + } + + public boolean isActionViewExpanded() { + return mIsActionViewExpanded; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemMule.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemMule.java new file mode 100644 index 000000000..5a8099832 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemMule.java @@ -0,0 +1,234 @@ +package com.actionbarsherlock.internal.view.menu; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.ActionProvider; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ContextMenu.ContextMenuInfo; + +/** Used to carry an instance of our version of MenuItem through a native channel. */ +public class MenuItemMule implements MenuItem { + private static final String ERROR = "Cannot interact with object designed for temporary " + + "instance passing. Make sure you using both SherlockFragmentActivity and " + + "SherlockFragment."; + + + private final com.actionbarsherlock.view.MenuItem mItem; + + public MenuItemMule(com.actionbarsherlock.view.MenuItem item) { + mItem = item; + } + + public com.actionbarsherlock.view.MenuItem unwrap() { + return mItem; + } + + + @Override + public boolean collapseActionView() { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean expandActionView() { + throw new IllegalStateException(ERROR); + } + + @Override + public ActionProvider getActionProvider() { + throw new IllegalStateException(ERROR); + } + + @Override + public View getActionView() { + throw new IllegalStateException(ERROR); + } + + @Override + public char getAlphabeticShortcut() { + throw new IllegalStateException(ERROR); + } + + @Override + public int getGroupId() { + throw new IllegalStateException(ERROR); + } + + @Override + public Drawable getIcon() { + throw new IllegalStateException(ERROR); + } + + @Override + public Intent getIntent() { + throw new IllegalStateException(ERROR); + } + + @Override + public int getItemId() { + throw new IllegalStateException(ERROR); + } + + @Override + public ContextMenuInfo getMenuInfo() { + throw new IllegalStateException(ERROR); + } + + @Override + public char getNumericShortcut() { + throw new IllegalStateException(ERROR); + } + + @Override + public int getOrder() { + throw new IllegalStateException(ERROR); + } + + @Override + public SubMenu getSubMenu() { + throw new IllegalStateException(ERROR); + } + + @Override + public CharSequence getTitle() { + throw new IllegalStateException(ERROR); + } + + @Override + public CharSequence getTitleCondensed() { + return mItem.getTitleCondensed(); + //throw new IllegalStateException(ERROR); + } + + @Override + public boolean hasSubMenu() { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean isActionViewExpanded() { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean isCheckable() { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean isChecked() { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean isEnabled() { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean isVisible() { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setActionProvider(ActionProvider arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setActionView(View arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setActionView(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setAlphabeticShortcut(char arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setCheckable(boolean arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setChecked(boolean arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setEnabled(boolean arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setIcon(Drawable arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setIcon(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setIntent(Intent arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setNumericShortcut(char arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setOnActionExpandListener(OnActionExpandListener arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setShortcut(char arg0, char arg1) { + throw new IllegalStateException(ERROR); + } + + @Override + public void setShowAsAction(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setShowAsActionFlags(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setTitle(CharSequence arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setTitle(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setTitleCondensed(CharSequence arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem setVisible(boolean arg0) { + throw new IllegalStateException(ERROR); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java new file mode 100644 index 000000000..d923c4ef8 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java @@ -0,0 +1,285 @@ +package com.actionbarsherlock.internal.view.menu; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ContextMenu.ContextMenuInfo; +import com.actionbarsherlock.internal.view.ActionProviderWrapper; +import com.actionbarsherlock.view.ActionProvider; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +public class MenuItemWrapper implements MenuItem, android.view.MenuItem.OnMenuItemClickListener, android.view.MenuItem.OnActionExpandListener { + private final android.view.MenuItem mNativeItem; + private SubMenu mSubMenu = null; + private OnMenuItemClickListener mMenuItemClickListener = null; + private OnActionExpandListener mActionExpandListener = null; + + + public MenuItemWrapper(android.view.MenuItem nativeItem) { + if (nativeItem == null) { + throw new IllegalStateException("Wrapped menu item cannot be null."); + } + mNativeItem = nativeItem; + } + + + @Override + public int getItemId() { + return mNativeItem.getItemId(); + } + + @Override + public int getGroupId() { + return mNativeItem.getGroupId(); + } + + @Override + public int getOrder() { + return mNativeItem.getOrder(); + } + + @Override + public MenuItem setTitle(CharSequence title) { + mNativeItem.setTitle(title); + return this; + } + + @Override + public MenuItem setTitle(int title) { + mNativeItem.setTitle(title); + return this; + } + + @Override + public CharSequence getTitle() { + return mNativeItem.getTitle(); + } + + @Override + public MenuItem setTitleCondensed(CharSequence title) { + mNativeItem.setTitleCondensed(title); + return this; + } + + @Override + public CharSequence getTitleCondensed() { + return mNativeItem.getTitleCondensed(); + } + + @Override + public MenuItem setIcon(Drawable icon) { + mNativeItem.setIcon(icon); + return this; + } + + @Override + public MenuItem setIcon(int iconRes) { + mNativeItem.setIcon(iconRes); + return this; + } + + @Override + public Drawable getIcon() { + return mNativeItem.getIcon(); + } + + @Override + public MenuItem setIntent(Intent intent) { + mNativeItem.setIntent(intent); + return this; + } + + @Override + public Intent getIntent() { + return mNativeItem.getIntent(); + } + + @Override + public MenuItem setShortcut(char numericChar, char alphaChar) { + mNativeItem.setShortcut(numericChar, alphaChar); + return this; + } + + @Override + public MenuItem setNumericShortcut(char numericChar) { + mNativeItem.setNumericShortcut(numericChar); + return this; + } + + @Override + public char getNumericShortcut() { + return mNativeItem.getNumericShortcut(); + } + + @Override + public MenuItem setAlphabeticShortcut(char alphaChar) { + mNativeItem.setAlphabeticShortcut(alphaChar); + return this; + } + + @Override + public char getAlphabeticShortcut() { + return mNativeItem.getAlphabeticShortcut(); + } + + @Override + public MenuItem setCheckable(boolean checkable) { + mNativeItem.setCheckable(checkable); + return this; + } + + @Override + public boolean isCheckable() { + return mNativeItem.isCheckable(); + } + + @Override + public MenuItem setChecked(boolean checked) { + mNativeItem.setChecked(checked); + return this; + } + + @Override + public boolean isChecked() { + return mNativeItem.isChecked(); + } + + @Override + public MenuItem setVisible(boolean visible) { + mNativeItem.setVisible(visible); + return this; + } + + @Override + public boolean isVisible() { + return mNativeItem.isVisible(); + } + + @Override + public MenuItem setEnabled(boolean enabled) { + mNativeItem.setEnabled(enabled); + return this; + } + + @Override + public boolean isEnabled() { + return mNativeItem.isEnabled(); + } + + @Override + public boolean hasSubMenu() { + return mNativeItem.hasSubMenu(); + } + + @Override + public SubMenu getSubMenu() { + if (hasSubMenu() && (mSubMenu == null)) { + mSubMenu = new SubMenuWrapper(mNativeItem.getSubMenu()); + } + return mSubMenu; + } + + @Override + public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) { + mMenuItemClickListener = menuItemClickListener; + //Register ourselves as the listener to proxy + mNativeItem.setOnMenuItemClickListener(this); + return this; + } + + @Override + public boolean onMenuItemClick(android.view.MenuItem item) { + if (mMenuItemClickListener != null) { + return mMenuItemClickListener.onMenuItemClick(this); + } + return false; + } + + @Override + public ContextMenuInfo getMenuInfo() { + return mNativeItem.getMenuInfo(); + } + + @Override + public void setShowAsAction(int actionEnum) { + mNativeItem.setShowAsAction(actionEnum); + } + + @Override + public MenuItem setShowAsActionFlags(int actionEnum) { + mNativeItem.setShowAsActionFlags(actionEnum); + return this; + } + + @Override + public MenuItem setActionView(View view) { + mNativeItem.setActionView(view); + return this; + } + + @Override + public MenuItem setActionView(int resId) { + mNativeItem.setActionView(resId); + return this; + } + + @Override + public View getActionView() { + return mNativeItem.getActionView(); + } + + @Override + public MenuItem setActionProvider(ActionProvider actionProvider) { + mNativeItem.setActionProvider(new ActionProviderWrapper(actionProvider)); + return this; + } + + @Override + public ActionProvider getActionProvider() { + android.view.ActionProvider nativeProvider = mNativeItem.getActionProvider(); + if (nativeProvider != null && nativeProvider instanceof ActionProviderWrapper) { + return ((ActionProviderWrapper)nativeProvider).unwrap(); + } + return null; + } + + @Override + public boolean expandActionView() { + return mNativeItem.expandActionView(); + } + + @Override + public boolean collapseActionView() { + return mNativeItem.collapseActionView(); + } + + @Override + public boolean isActionViewExpanded() { + return mNativeItem.isActionViewExpanded(); + } + + @Override + public MenuItem setOnActionExpandListener(OnActionExpandListener listener) { + mActionExpandListener = listener; + //Register ourselves as the listener to proxy + mNativeItem.setOnActionExpandListener(this); + return this; + } + + @Override + public boolean onMenuItemActionCollapse(android.view.MenuItem item) { + if (mActionExpandListener != null) { + return mActionExpandListener.onMenuItemActionCollapse(this); + } + return false; + } + + @Override + public boolean onMenuItemActionExpand(android.view.MenuItem item) { + if (mActionExpandListener != null) { + return mActionExpandListener.onMenuItemActionExpand(this); + } + return false; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuMule.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuMule.java new file mode 100644 index 000000000..183e87a56 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuMule.java @@ -0,0 +1,150 @@ +package com.actionbarsherlock.internal.view.menu; + +import android.content.ComponentName; +import android.content.Intent; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; + +/** Used to carry an instance of our version of Menu through a native channel. */ +public class MenuMule implements Menu { + private static final String ERROR = "Cannot interact with object designed for temporary " + + "instance passing. Make sure you using both SherlockFragmentActivity and " + + "SherlockFragment."; + + + private final com.actionbarsherlock.view.Menu mMenu; + + public MenuMule(com.actionbarsherlock.view.Menu menu) { + mMenu = menu; + } + + public com.actionbarsherlock.view.Menu unwrap() { + return mMenu; + } + + + @Override + public MenuItem add(CharSequence arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem add(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem add(int arg0, int arg1, int arg2, CharSequence arg3) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem add(int arg0, int arg1, int arg2, int arg3) { + throw new IllegalStateException(ERROR); + } + + @Override + public int addIntentOptions(int arg0, int arg1, int arg2, + ComponentName arg3, Intent[] arg4, Intent arg5, int arg6, + MenuItem[] arg7) { + throw new IllegalStateException(ERROR); + } + + @Override + public SubMenu addSubMenu(CharSequence arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public SubMenu addSubMenu(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public SubMenu addSubMenu(int arg0, int arg1, int arg2, CharSequence arg3) { + throw new IllegalStateException(ERROR); + } + + @Override + public SubMenu addSubMenu(int arg0, int arg1, int arg2, int arg3) { + throw new IllegalStateException(ERROR); + } + + @Override + public void clear() { + throw new IllegalStateException(ERROR); + } + + @Override + public void close() { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem findItem(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public MenuItem getItem(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean hasVisibleItems() { + return mMenu.hasVisibleItems(); + //throw new IllegalStateException(ERROR); + } + + @Override + public boolean isShortcutKey(int arg0, KeyEvent arg1) { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean performIdentifierAction(int arg0, int arg1) { + throw new IllegalStateException(ERROR); + } + + @Override + public boolean performShortcut(int arg0, KeyEvent arg1, int arg2) { + throw new IllegalStateException(ERROR); + } + + @Override + public void removeGroup(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public void removeItem(int arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public void setGroupCheckable(int arg0, boolean arg1, boolean arg2) { + throw new IllegalStateException(ERROR); + } + + @Override + public void setGroupEnabled(int arg0, boolean arg1) { + throw new IllegalStateException(ERROR); + } + + @Override + public void setGroupVisible(int arg0, boolean arg1) { + throw new IllegalStateException(ERROR); + } + + @Override + public void setQwertyMode(boolean arg0) { + throw new IllegalStateException(ERROR); + } + + @Override + public int size() { + throw new IllegalStateException(ERROR); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java new file mode 100644 index 000000000..f030de310 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import java.util.ArrayList; +import android.content.Context; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.os.Parcelable; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ListAdapter; +import android.widget.PopupWindow; +import com.actionbarsherlock.R; +import com.actionbarsherlock.internal.view.View_HasStateListenerSupport; +import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener; +import com.actionbarsherlock.internal.widget.IcsListPopupWindow; +import com.actionbarsherlock.view.MenuItem; + +/** + * Presents a menu as a small, simple popup anchored to another view. + * @hide + */ +public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener, + ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener, + View_OnAttachStateChangeListener, MenuPresenter { + //UNUSED private static final String TAG = "MenuPopupHelper"; + + static final int ITEM_LAYOUT = R.layout.abs__popup_menu_item_layout; + + private Context mContext; + private LayoutInflater mInflater; + private IcsListPopupWindow mPopup; + private MenuBuilder mMenu; + private int mPopupMaxWidth; + private View mAnchorView; + private boolean mOverflowOnly; + private ViewTreeObserver mTreeObserver; + + private MenuAdapter mAdapter; + + private Callback mPresenterCallback; + + boolean mForceShowIcon; + + private ViewGroup mMeasureParent; + + public MenuPopupHelper(Context context, MenuBuilder menu) { + this(context, menu, null, false); + } + + public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { + this(context, menu, anchorView, false); + } + + public MenuPopupHelper(Context context, MenuBuilder menu, + View anchorView, boolean overflowOnly) { + mContext = context; + mInflater = LayoutInflater.from(context); + mMenu = menu; + mOverflowOnly = overflowOnly; + + final Resources res = context.getResources(); + mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, + res.getDimensionPixelSize(R.dimen.abs__config_prefDialogWidth)); + + mAnchorView = anchorView; + + menu.addMenuPresenter(this); + } + + public void setAnchorView(View anchor) { + mAnchorView = anchor; + } + + public void setForceShowIcon(boolean forceShow) { + mForceShowIcon = forceShow; + } + + public void show() { + if (!tryShow()) { + throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor"); + } + } + + public boolean tryShow() { + mPopup = new IcsListPopupWindow(mContext, null, R.attr.popupMenuStyle); + mPopup.setOnDismissListener(this); + mPopup.setOnItemClickListener(this); + + mAdapter = new MenuAdapter(mMenu); + mPopup.setAdapter(mAdapter); + mPopup.setModal(true); + + View anchor = mAnchorView; + if (anchor != null) { + final boolean addGlobalListener = mTreeObserver == null; + mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest + if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this); + ((View_HasStateListenerSupport)anchor).addOnAttachStateChangeListener(this); + mPopup.setAnchorView(anchor); + } else { + return false; + } + + mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth)); + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + mPopup.show(); + mPopup.getListView().setOnKeyListener(this); + return true; + } + + public void dismiss() { + if (isShowing()) { + mPopup.dismiss(); + } + } + + public void onDismiss() { + mPopup = null; + mMenu.close(); + if (mTreeObserver != null) { + if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver(); + mTreeObserver.removeGlobalOnLayoutListener(this); + mTreeObserver = null; + } + ((View_HasStateListenerSupport)mAnchorView).removeOnAttachStateChangeListener(this); + } + + public boolean isShowing() { + return mPopup != null && mPopup.isShowing(); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + MenuAdapter adapter = mAdapter; + adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0); + } + + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { + dismiss(); + return true; + } + return false; + } + + private int measureContentWidth(ListAdapter adapter) { + // Menus don't tend to be long, so this is more sane than it looks. + int width = 0; + View itemView = null; + int itemType = 0; + final int widthMeasureSpec = + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int count = adapter.getCount(); + for (int i = 0; i < count; i++) { + final int positionType = adapter.getItemViewType(i); + if (positionType != itemType) { + itemType = positionType; + itemView = null; + } + if (mMeasureParent == null) { + mMeasureParent = new FrameLayout(mContext); + } + itemView = adapter.getView(i, itemView, mMeasureParent); + itemView.measure(widthMeasureSpec, heightMeasureSpec); + width = Math.max(width, itemView.getMeasuredWidth()); + } + return width; + } + + @Override + public void onGlobalLayout() { + if (isShowing()) { + final View anchor = mAnchorView; + if (anchor == null || !anchor.isShown()) { + dismiss(); + } else if (isShowing()) { + // Recompute window size and position + mPopup.show(); + } + } + } + + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + if (mTreeObserver != null) { + if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver(); + mTreeObserver.removeGlobalOnLayoutListener(this); + } + ((View_HasStateListenerSupport)v).removeOnAttachStateChangeListener(this); + } + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + // Don't need to do anything; we added as a presenter in the constructor. + } + + @Override + public MenuView getMenuView(ViewGroup root) { + throw new UnsupportedOperationException("MenuPopupHelpers manage their own views"); + } + + @Override + public void updateMenuView(boolean cleared) { + if (mAdapter != null) mAdapter.notifyDataSetChanged(); + } + + @Override + public void setCallback(Callback cb) { + mPresenterCallback = cb; + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + if (subMenu.hasVisibleItems()) { + MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false); + subPopup.setCallback(mPresenterCallback); + + boolean preserveIconSpacing = false; + final int count = subMenu.size(); + for (int i = 0; i < count; i++) { + MenuItem childItem = subMenu.getItem(i); + if (childItem.isVisible() && childItem.getIcon() != null) { + preserveIconSpacing = true; + break; + } + } + subPopup.setForceShowIcon(preserveIconSpacing); + + if (subPopup.tryShow()) { + if (mPresenterCallback != null) { + mPresenterCallback.onOpenSubMenu(subMenu); + } + return true; + } + } + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + // Only care about the (sub)menu we're presenting. + if (menu != mMenu) return; + + dismiss(); + if (mPresenterCallback != null) { + mPresenterCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + @Override + public boolean flagActionItems() { + return false; + } + + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + @Override + public int getId() { + return 0; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + + private class MenuAdapter extends BaseAdapter { + private MenuBuilder mAdapterMenu; + private int mExpandedIndex = -1; + + public MenuAdapter(MenuBuilder menu) { + mAdapterMenu = menu; + registerDataSetObserver(new ExpandedIndexObserver()); + findExpandedIndex(); + } + + public int getCount() { + ArrayList items = mOverflowOnly ? + mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); + if (mExpandedIndex < 0) { + return items.size(); + } + return items.size() - 1; + } + + public MenuItemImpl getItem(int position) { + ArrayList items = mOverflowOnly ? + mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); + if (mExpandedIndex >= 0 && position >= mExpandedIndex) { + position++; + } + return items.get(position); + } + + public long getItemId(int position) { + // Since a menu item's ID is optional, we'll use the position as an + // ID for the item in the AdapterView + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); + } + + MenuView.ItemView itemView = (MenuView.ItemView) convertView; + if (mForceShowIcon) { + ((ListMenuItemView) convertView).setForceShowIcon(true); + } + itemView.initialize(getItem(position), 0); + return convertView; + } + + void findExpandedIndex() { + final MenuItemImpl expandedItem = mMenu.getExpandedItem(); + if (expandedItem != null) { + final ArrayList items = mMenu.getNonActionItems(); + final int count = items.size(); + for (int i = 0; i < count; i++) { + final MenuItemImpl item = items.get(i); + if (item == expandedItem) { + mExpandedIndex = i; + return; + } + } + } + mExpandedIndex = -1; + } + } + + private class ExpandedIndexObserver extends DataSetObserver { + @Override + public void onChanged() { + mAdapter.findExpandedIndex(); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java new file mode 100644 index 000000000..c3f35472c --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import android.content.Context; +import android.os.Parcelable; +import android.view.ViewGroup; + +/** + * A MenuPresenter is responsible for building views for a Menu object. + * It takes over some responsibility from the old style monolithic MenuBuilder class. + */ +public interface MenuPresenter { + /** + * Called by menu implementation to notify another component of open/close events. + */ + public interface Callback { + /** + * Called when a menu is closing. + * @param menu + * @param allMenusAreClosing + */ + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing); + + /** + * Called when a submenu opens. Useful for notifying the application + * of menu state so that it does not attempt to hide the action bar + * while a submenu is open or similar. + * + * @param subMenu Submenu currently being opened + * @return true if the Callback will handle presenting the submenu, false if + * the presenter should attempt to do so. + */ + public boolean onOpenSubMenu(MenuBuilder subMenu); + } + + /** + * Initialize this presenter for the given context and menu. + * This method is called by MenuBuilder when a presenter is + * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)} + * + * @param context Context for this presenter; used for view creation and resource management + * @param menu Menu to host + */ + public void initForMenu(Context context, MenuBuilder menu); + + /** + * Retrieve a MenuView to display the menu specified in + * {@link #initForMenu(Context, Menu)}. + * + * @param root Intended parent of the MenuView. + * @return A freshly created MenuView. + */ + public MenuView getMenuView(ViewGroup root); + + /** + * Update the menu UI in response to a change. Called by + * MenuBuilder during the normal course of operation. + * + * @param cleared true if the menu was entirely cleared + */ + public void updateMenuView(boolean cleared); + + /** + * Set a callback object that will be notified of menu events + * related to this specific presentation. + * @param cb Callback that will be notified of future events + */ + public void setCallback(Callback cb); + + /** + * Called by Menu implementations to indicate that a submenu item + * has been selected. An active Callback should be notified, and + * if applicable the presenter should present the submenu. + * + * @param subMenu SubMenu being opened + * @return true if the the event was handled, false otherwise. + */ + public boolean onSubMenuSelected(SubMenuBuilder subMenu); + + /** + * Called by Menu implementations to indicate that a menu or submenu is + * closing. Presenter implementations should close the representation + * of the menu indicated as necessary and notify a registered callback. + * + * @param menu Menu or submenu that is closing. + * @param allMenusAreClosing True if all associated menus are closing. + */ + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing); + + /** + * Called by Menu implementations to flag items that will be shown as actions. + * @return true if this presenter changed the action status of any items. + */ + public boolean flagActionItems(); + + /** + * Called when a menu item with a collapsable action view should expand its action view. + * + * @param menu Menu containing the item to be expanded + * @param item Item to be expanded + * @return true if this presenter expanded the action view, false otherwise. + */ + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item); + + /** + * Called when a menu item with a collapsable action view should collapse its action view. + * + * @param menu Menu containing the item to be collapsed + * @param item Item to be collapsed + * @return true if this presenter collapsed the action view, false otherwise. + */ + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item); + + /** + * Returns an ID for determining how to save/restore instance state. + * @return a valid ID value. + */ + public int getId(); + + /** + * Returns a Parcelable describing the current state of the presenter. + * It will be passed to the {@link #onRestoreInstanceState(Parcelable)} + * method of the presenter sharing the same ID later. + * @return The saved instance state + */ + public Parcelable onSaveInstanceState(); + + /** + * Supplies the previously saved instance state to be restored. + * @param state The previously saved instance state + */ + public void onRestoreInstanceState(Parcelable state); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java new file mode 100644 index 000000000..323ba2d88 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import android.graphics.drawable.Drawable; + +/** + * Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the + * menu to be functional. + * + * @hide + */ +public interface MenuView { + /** + * Initializes the menu to the given menu. This should be called after the + * view is inflated. + * + * @param menu The menu that this MenuView should display. + */ + public void initialize(MenuBuilder menu); + + /** + * Returns the default animations to be used for this menu when entering/exiting. + * @return A resource ID for the default animations to be used for this menu. + */ + public int getWindowAnimations(); + + /** + * Minimal interface for a menu item view. {@link #initialize(MenuItemImpl, int)} must be called + * for the item to be functional. + */ + public interface ItemView { + /** + * Initializes with the provided MenuItemData. This should be called after the view is + * inflated. + * @param itemData The item that this ItemView should display. + * @param menuType The type of this menu, one of + * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED}, + * {@link MenuBuilder#TYPE_DIALOG}). + */ + public void initialize(MenuItemImpl itemData, int menuType); + + /** + * Gets the item data that this view is displaying. + * @return the item data, or null if there is not one + */ + public MenuItemImpl getItemData(); + + /** + * Sets the title of the item view. + * @param title The title to set. + */ + public void setTitle(CharSequence title); + + /** + * Sets the enabled state of the item view. + * @param enabled Whether the item view should be enabled. + */ + public void setEnabled(boolean enabled); + + /** + * Displays the checkbox for the item view. This does not ensure the item view will be + * checked, for that use {@link #setChecked}. + * @param checkable Whether to display the checkbox or to hide it + */ + public void setCheckable(boolean checkable); + + /** + * Checks the checkbox for the item view. If the checkbox is hidden, it will NOT be + * made visible, call {@link #setCheckable(boolean)} for that. + * @param checked Whether the checkbox should be checked + */ + public void setChecked(boolean checked); + + /** + * Sets the shortcut for the item. + * @param showShortcut Whether a shortcut should be shown(if false, the value of + * shortcutKey should be ignored). + * @param shortcutKey The shortcut key that should be shown on the ItemView. + */ + public void setShortcut(boolean showShortcut, char shortcutKey); + + /** + * Set the icon of this item view. + * @param icon The icon of this item. null to hide the icon. + */ + public void setIcon(Drawable icon); + + /** + * Whether this item view prefers displaying the condensed title rather + * than the normal title. If a condensed title is not available, the + * normal title will be used. + * + * @return Whether this item view prefers displaying the condensed + * title. + */ + public boolean prefersCondensedTitle(); + + /** + * Whether this item view shows an icon. + * + * @return Whether this item view shows an icon. + */ + public boolean showsIcon(); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java new file mode 100644 index 000000000..64fc4aeaa --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java @@ -0,0 +1,180 @@ +package com.actionbarsherlock.internal.view.menu; + +import java.util.WeakHashMap; +import android.content.ComponentName; +import android.content.Intent; +import android.view.KeyEvent; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +public class MenuWrapper implements Menu { + private final android.view.Menu mNativeMenu; + + private final WeakHashMap mNativeMap = + new WeakHashMap(); + + + public MenuWrapper(android.view.Menu nativeMenu) { + mNativeMenu = nativeMenu; + } + + public android.view.Menu unwrap() { + return mNativeMenu; + } + + private MenuItem addInternal(android.view.MenuItem nativeItem) { + MenuItem item = new MenuItemWrapper(nativeItem); + mNativeMap.put(nativeItem, item); + return item; + } + + @Override + public MenuItem add(CharSequence title) { + return addInternal(mNativeMenu.add(title)); + } + + @Override + public MenuItem add(int titleRes) { + return addInternal(mNativeMenu.add(titleRes)); + } + + @Override + public MenuItem add(int groupId, int itemId, int order, CharSequence title) { + return addInternal(mNativeMenu.add(groupId, itemId, order, title)); + } + + @Override + public MenuItem add(int groupId, int itemId, int order, int titleRes) { + return addInternal(mNativeMenu.add(groupId, itemId, order, titleRes)); + } + + private SubMenu addInternal(android.view.SubMenu nativeSubMenu) { + SubMenu subMenu = new SubMenuWrapper(nativeSubMenu); + android.view.MenuItem nativeItem = nativeSubMenu.getItem(); + MenuItem item = subMenu.getItem(); + mNativeMap.put(nativeItem, item); + return subMenu; + } + + @Override + public SubMenu addSubMenu(CharSequence title) { + return addInternal(mNativeMenu.addSubMenu(title)); + } + + @Override + public SubMenu addSubMenu(int titleRes) { + return addInternal(mNativeMenu.addSubMenu(titleRes)); + } + + @Override + public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) { + return addInternal(mNativeMenu.addSubMenu(groupId, itemId, order, title)); + } + + @Override + public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) { + return addInternal(mNativeMenu.addSubMenu(groupId, itemId, order, titleRes)); + } + + @Override + public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) { + android.view.MenuItem[] nativeOutItems = new android.view.MenuItem[outSpecificItems.length]; + int result = mNativeMenu.addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, nativeOutItems); + for (int i = 0, length = outSpecificItems.length; i < length; i++) { + outSpecificItems[i] = new MenuItemWrapper(nativeOutItems[i]); + } + return result; + } + + @Override + public void removeItem(int id) { + mNativeMenu.removeItem(id); + } + + @Override + public void removeGroup(int groupId) { + mNativeMenu.removeGroup(groupId); + } + + @Override + public void clear() { + mNativeMap.clear(); + mNativeMenu.clear(); + } + + @Override + public void setGroupCheckable(int group, boolean checkable, boolean exclusive) { + mNativeMenu.setGroupCheckable(group, checkable, exclusive); + } + + @Override + public void setGroupVisible(int group, boolean visible) { + mNativeMenu.setGroupVisible(group, visible); + } + + @Override + public void setGroupEnabled(int group, boolean enabled) { + mNativeMenu.setGroupEnabled(group, enabled); + } + + @Override + public boolean hasVisibleItems() { + return mNativeMenu.hasVisibleItems(); + } + + @Override + public MenuItem findItem(int id) { + android.view.MenuItem nativeItem = mNativeMenu.findItem(id); + return findItem(nativeItem); + } + + public MenuItem findItem(android.view.MenuItem nativeItem) { + if (nativeItem == null) { + return null; + } + + MenuItem wrapped = mNativeMap.get(nativeItem); + if (wrapped != null) { + return wrapped; + } + + return addInternal(nativeItem); + } + + @Override + public int size() { + return mNativeMenu.size(); + } + + @Override + public MenuItem getItem(int index) { + android.view.MenuItem nativeItem = mNativeMenu.getItem(index); + return findItem(nativeItem); + } + + @Override + public void close() { + mNativeMenu.close(); + } + + @Override + public boolean performShortcut(int keyCode, KeyEvent event, int flags) { + return mNativeMenu.performShortcut(keyCode, event, flags); + } + + @Override + public boolean isShortcutKey(int keyCode, KeyEvent event) { + return mNativeMenu.isShortcutKey(keyCode, event); + } + + @Override + public boolean performIdentifierAction(int id, int flags) { + return mNativeMenu.performIdentifierAction(id, flags); + } + + @Override + public void setQwertyMode(boolean isQwerty) { + mNativeMenu.setQwertyMode(isQwerty); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java new file mode 100644 index 000000000..6679cf386 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.view.menu; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +/** + * The model for a sub menu, which is an extension of the menu. Most methods are proxied to + * the parent menu. + */ +public class SubMenuBuilder extends MenuBuilder implements SubMenu { + private MenuBuilder mParentMenu; + private MenuItemImpl mItem; + + public SubMenuBuilder(Context context, MenuBuilder parentMenu, MenuItemImpl item) { + super(context); + + mParentMenu = parentMenu; + mItem = item; + } + + @Override + public void setQwertyMode(boolean isQwerty) { + mParentMenu.setQwertyMode(isQwerty); + } + + @Override + public boolean isQwertyMode() { + return mParentMenu.isQwertyMode(); + } + + @Override + public void setShortcutsVisible(boolean shortcutsVisible) { + mParentMenu.setShortcutsVisible(shortcutsVisible); + } + + @Override + public boolean isShortcutsVisible() { + return mParentMenu.isShortcutsVisible(); + } + + public Menu getParentMenu() { + return mParentMenu; + } + + public MenuItem getItem() { + return mItem; + } + + @Override + public void setCallback(Callback callback) { + mParentMenu.setCallback(callback); + } + + @Override + public MenuBuilder getRootMenu() { + return mParentMenu; + } + + @Override + boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) { + return super.dispatchMenuItemSelected(menu, item) || + mParentMenu.dispatchMenuItemSelected(menu, item); + } + + public SubMenu setIcon(Drawable icon) { + mItem.setIcon(icon); + return this; + } + + public SubMenu setIcon(int iconRes) { + mItem.setIcon(iconRes); + return this; + } + + public SubMenu setHeaderIcon(Drawable icon) { + return (SubMenu) super.setHeaderIconInt(icon); + } + + public SubMenu setHeaderIcon(int iconRes) { + return (SubMenu) super.setHeaderIconInt(iconRes); + } + + public SubMenu setHeaderTitle(CharSequence title) { + return (SubMenu) super.setHeaderTitleInt(title); + } + + public SubMenu setHeaderTitle(int titleRes) { + return (SubMenu) super.setHeaderTitleInt(titleRes); + } + + public SubMenu setHeaderView(View view) { + return (SubMenu) super.setHeaderViewInt(view); + } + + @Override + public boolean expandItemActionView(MenuItemImpl item) { + return mParentMenu.expandItemActionView(item); + } + + @Override + public boolean collapseItemActionView(MenuItemImpl item) { + return mParentMenu.collapseItemActionView(item); + } + + @Override + public String getActionViewStatesKey() { + final int itemId = mItem != null ? mItem.getItemId() : 0; + if (itemId == 0) { + return null; + } + return super.getActionViewStatesKey() + ":" + itemId; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java new file mode 100644 index 000000000..7d307acb1 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java @@ -0,0 +1,72 @@ +package com.actionbarsherlock.internal.view.menu; + +import android.graphics.drawable.Drawable; +import android.view.View; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SubMenu; + +public class SubMenuWrapper extends MenuWrapper implements SubMenu { + private final android.view.SubMenu mNativeSubMenu; + private MenuItem mItem = null; + + public SubMenuWrapper(android.view.SubMenu nativeSubMenu) { + super(nativeSubMenu); + mNativeSubMenu = nativeSubMenu; + } + + + @Override + public SubMenu setHeaderTitle(int titleRes) { + mNativeSubMenu.setHeaderTitle(titleRes); + return this; + } + + @Override + public SubMenu setHeaderTitle(CharSequence title) { + mNativeSubMenu.setHeaderTitle(title); + return this; + } + + @Override + public SubMenu setHeaderIcon(int iconRes) { + mNativeSubMenu.setHeaderIcon(iconRes); + return this; + } + + @Override + public SubMenu setHeaderIcon(Drawable icon) { + mNativeSubMenu.setHeaderIcon(icon); + return this; + } + + @Override + public SubMenu setHeaderView(View view) { + mNativeSubMenu.setHeaderView(view); + return this; + } + + @Override + public void clearHeader() { + mNativeSubMenu.clearHeader(); + } + + @Override + public SubMenu setIcon(int iconRes) { + mNativeSubMenu.setIcon(iconRes); + return this; + } + + @Override + public SubMenu setIcon(Drawable icon) { + mNativeSubMenu.setIcon(icon); + return this; + } + + @Override + public MenuItem getItem() { + if (mItem == null) { + mItem = new MenuItemWrapper(mNativeSubMenu.getItem()); + } + return mItem; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/AbsActionBarView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/AbsActionBarView.java new file mode 100644 index 000000000..3a4a44675 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/AbsActionBarView.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator; +import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet; +import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator; +import com.actionbarsherlock.internal.nineoldandroids.view.NineViewGroup; +import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter; +import com.actionbarsherlock.internal.view.menu.ActionMenuView; + +import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean; + +public abstract class AbsActionBarView extends NineViewGroup { + protected ActionMenuView mMenuView; + protected ActionMenuPresenter mActionMenuPresenter; + protected ActionBarContainer mSplitView; + protected boolean mSplitActionBar; + protected boolean mSplitWhenNarrow; + protected int mContentHeight; + + final Context mContext; + + protected Animator mVisibilityAnim; + protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); + + private static final /*Time*/Interpolator sAlphaInterpolator = new DecelerateInterpolator(); + + private static final int FADE_DURATION = 200; + + public AbsActionBarView(Context context) { + super(context); + mContext = context; + } + + public AbsActionBarView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + } + + public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mContext = context; + } + + /* + * Must be public so we can dispatch pre-2.2 via ActionBarImpl. + */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { + super.onConfigurationChanged(newConfig); + } else if (mMenuView != null) { + mMenuView.onConfigurationChanged(newConfig); + } + + // Action bar can change size on configuration changes. + // Reread the desired height from the theme-specified style. + TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar, + R.attr.actionBarStyle, 0); + setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0)); + a.recycle(); + if (mSplitWhenNarrow) { + setSplitActionBar(getResources_getBoolean(getContext(), + R.bool.abs__split_action_bar_is_narrow)); + } + if (mActionMenuPresenter != null) { + mActionMenuPresenter.onConfigurationChanged(newConfig); + } + } + + /** + * Sets whether the bar should be split right now, no questions asked. + * @param split true if the bar should split + */ + public void setSplitActionBar(boolean split) { + mSplitActionBar = split; + } + + /** + * Sets whether the bar should split if we enter a narrow screen configuration. + * @param splitWhenNarrow true if the bar should check to split after a config change + */ + public void setSplitWhenNarrow(boolean splitWhenNarrow) { + mSplitWhenNarrow = splitWhenNarrow; + } + + public void setContentHeight(int height) { + mContentHeight = height; + requestLayout(); + } + + public int getContentHeight() { + return mContentHeight; + } + + public void setSplitView(ActionBarContainer splitView) { + mSplitView = splitView; + } + + /** + * @return Current visibility or if animating, the visibility being animated to. + */ + public int getAnimatedVisibility() { + if (mVisibilityAnim != null) { + return mVisAnimListener.mFinalVisibility; + } + return getVisibility(); + } + + public void animateToVisibility(int visibility) { + if (mVisibilityAnim != null) { + mVisibilityAnim.cancel(); + } + if (visibility == VISIBLE) { + if (getVisibility() != VISIBLE) { + setAlpha(0); + if (mSplitView != null && mMenuView != null) { + mMenuView.setAlpha(0); + } + } + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + if (mSplitView != null && mMenuView != null) { + AnimatorSet set = new AnimatorSet(); + ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 1); + splitAnim.setDuration(FADE_DURATION); + set.addListener(mVisAnimListener.withFinalVisibility(visibility)); + set.play(anim).with(splitAnim); + set.start(); + } else { + anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); + anim.start(); + } + } else { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + if (mSplitView != null && mMenuView != null) { + AnimatorSet set = new AnimatorSet(); + ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 0); + splitAnim.setDuration(FADE_DURATION); + set.addListener(mVisAnimListener.withFinalVisibility(visibility)); + set.play(anim).with(splitAnim); + set.start(); + } else { + anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); + anim.start(); + } + } + } + + @Override + public void setVisibility(int visibility) { + if (mVisibilityAnim != null) { + mVisibilityAnim.end(); + } + super.setVisibility(visibility); + } + + public boolean showOverflowMenu() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.showOverflowMenu(); + } + return false; + } + + public void postShowOverflowMenu() { + post(new Runnable() { + public void run() { + showOverflowMenu(); + } + }); + } + + public boolean hideOverflowMenu() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.hideOverflowMenu(); + } + return false; + } + + public boolean isOverflowMenuShowing() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.isOverflowMenuShowing(); + } + return false; + } + + public boolean isOverflowReserved() { + return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); + } + + public void dismissPopupMenus() { + if (mActionMenuPresenter != null) { + mActionMenuPresenter.dismissPopupMenus(); + } + } + + protected int measureChildView(View child, int availableWidth, int childSpecHeight, + int spacing) { + child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), + childSpecHeight); + + availableWidth -= child.getMeasuredWidth(); + availableWidth -= spacing; + + return Math.max(0, availableWidth); + } + + protected int positionChild(View child, int x, int y, int contentHeight) { + int childWidth = child.getMeasuredWidth(); + int childHeight = child.getMeasuredHeight(); + int childTop = y + (contentHeight - childHeight) / 2; + + child.layout(x, childTop, x + childWidth, childTop + childHeight); + + return childWidth; + } + + protected int positionChildInverse(View child, int x, int y, int contentHeight) { + int childWidth = child.getMeasuredWidth(); + int childHeight = child.getMeasuredHeight(); + int childTop = y + (contentHeight - childHeight) / 2; + + child.layout(x - childWidth, childTop, x, childTop + childHeight); + + return childWidth; + } + + protected class VisibilityAnimListener implements Animator.AnimatorListener { + private boolean mCanceled = false; + int mFinalVisibility; + + public VisibilityAnimListener withFinalVisibility(int visibility) { + mFinalVisibility = visibility; + return this; + } + + @Override + public void onAnimationStart(Animator animation) { + setVisibility(VISIBLE); + mVisibilityAnim = animation; + mCanceled = false; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) return; + + mVisibilityAnim = null; + setVisibility(mFinalVisibility); + if (mSplitView != null && mMenuView != null) { + mMenuView.setVisibility(mFinalVisibility); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java new file mode 100644 index 000000000..5e5aa2867 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.internal.nineoldandroids.widget.NineFrameLayout; + +/** + * This class acts as a container for the action bar view and action mode context views. + * It applies special styles as needed to help handle animated transitions between them. + * @hide + */ +public class ActionBarContainer extends NineFrameLayout { + private boolean mIsTransitioning; + private View mTabContainer; + private ActionBarView mActionBarView; + + private Drawable mBackground; + private Drawable mStackedBackground; + private Drawable mSplitBackground; + private boolean mIsSplit; + private boolean mIsStacked; + + public ActionBarContainer(Context context) { + this(context, null); + } + + public ActionBarContainer(Context context, AttributeSet attrs) { + super(context, attrs); + + setBackgroundDrawable(null); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SherlockActionBar); + mBackground = a.getDrawable(R.styleable.SherlockActionBar_background); + mStackedBackground = a.getDrawable( + R.styleable.SherlockActionBar_backgroundStacked); + + if (getId() == R.id.abs__split_action_bar) { + mIsSplit = true; + mSplitBackground = a.getDrawable( + R.styleable.SherlockActionBar_backgroundSplit); + } + a.recycle(); + + setWillNotDraw(mIsSplit ? mSplitBackground == null : + mBackground == null && mStackedBackground == null); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + mActionBarView = (ActionBarView) findViewById(R.id.abs__action_bar); + } + + public void setPrimaryBackground(Drawable bg) { + mBackground = bg; + invalidate(); + } + + public void setStackedBackground(Drawable bg) { + mStackedBackground = bg; + invalidate(); + } + + public void setSplitBackground(Drawable bg) { + mSplitBackground = bg; + invalidate(); + } + + /** + * Set the action bar into a "transitioning" state. While transitioning + * the bar will block focus and touch from all of its descendants. This + * prevents the user from interacting with the bar while it is animating + * in or out. + * + * @param isTransitioning true if the bar is currently transitioning, false otherwise. + */ + public void setTransitioning(boolean isTransitioning) { + mIsTransitioning = isTransitioning; + setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS + : FOCUS_AFTER_DESCENDANTS); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mIsTransitioning || super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + super.onTouchEvent(ev); + + // An action bar always eats touch events. + return true; + } + + @Override + public boolean onHoverEvent(MotionEvent ev) { + super.onHoverEvent(ev); + + // An action bar always eats hover events. + return true; + } + + public void setTabContainer(ScrollingTabContainerView tabView) { + if (mTabContainer != null) { + removeView(mTabContainer); + } + mTabContainer = tabView; + if (tabView != null) { + addView(tabView); + final ViewGroup.LayoutParams lp = tabView.getLayoutParams(); + lp.width = LayoutParams.MATCH_PARENT; + lp.height = LayoutParams.WRAP_CONTENT; + tabView.setAllowCollapse(false); + } + } + + public View getTabContainer() { + return mTabContainer; + } + + @Override + public void onDraw(Canvas canvas) { + if (getWidth() == 0 || getHeight() == 0) { + return; + } + + if (mIsSplit) { + if (mSplitBackground != null) mSplitBackground.draw(canvas); + } else { + if (mBackground != null) { + mBackground.draw(canvas); + } + if (mStackedBackground != null && mIsStacked) { + mStackedBackground.draw(canvas); + } + } + } + + //This causes the animation reflection to fail on pre-HC platforms + //@Override + //public android.view.ActionMode startActionModeForChild(View child, android.view.ActionMode.Callback callback) { + // // No starting an action mode for an action bar child! (Where would it go?) + // return null; + //} + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (mActionBarView == null) return; + + final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams(); + final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 : + mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; + + if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { + final int mode = MeasureSpec.getMode(heightMeasureSpec); + if (mode == MeasureSpec.AT_MOST) { + final int maxHeight = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(getMeasuredWidth(), + Math.min(actionBarViewHeight + mTabContainer.getMeasuredHeight(), + maxHeight)); + } + } + } + + @Override + public void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE; + + if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { + final int containerHeight = getMeasuredHeight(); + final int tabHeight = mTabContainer.getMeasuredHeight(); + + if ((mActionBarView.getDisplayOptions() & ActionBar.DISPLAY_SHOW_HOME) == 0) { + // Not showing home, put tabs on top. + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + + if (child == mTabContainer) continue; + + if (!mActionBarView.isCollapsed()) { + child.offsetTopAndBottom(tabHeight); + } + } + mTabContainer.layout(l, 0, r, tabHeight); + } else { + mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight); + } + } + + boolean needsInvalidate = false; + if (mIsSplit) { + if (mSplitBackground != null) { + mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); + needsInvalidate = true; + } + } else { + if (mBackground != null) { + mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), + mActionBarView.getRight(), mActionBarView.getBottom()); + needsInvalidate = true; + } + if ((mIsStacked = hasTabs && mStackedBackground != null)) { + mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(), + mTabContainer.getRight(), mTabContainer.getBottom()); + needsInvalidate = true; + } + } + + if (needsInvalidate) { + invalidate(); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContextView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContextView.java new file mode 100644 index 000000000..9ec250f38 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarContextView.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.DecelerateInterpolator; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator.AnimatorListener; +import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet; +import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator; +import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorProxy; +import com.actionbarsherlock.internal.nineoldandroids.widget.NineLinearLayout; +import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter; +import com.actionbarsherlock.internal.view.menu.ActionMenuView; +import com.actionbarsherlock.internal.view.menu.MenuBuilder; +import com.actionbarsherlock.view.ActionMode; + +/** + * @hide + */ +public class ActionBarContextView extends AbsActionBarView implements AnimatorListener { + //UNUSED private static final String TAG = "ActionBarContextView"; + + private CharSequence mTitle; + private CharSequence mSubtitle; + + private NineLinearLayout mClose; + private View mCustomView; + private LinearLayout mTitleLayout; + private TextView mTitleView; + private TextView mSubtitleView; + private int mTitleStyleRes; + private int mSubtitleStyleRes; + private Drawable mSplitBackground; + + private Animator mCurrentAnimation; + private boolean mAnimateInOnLayout; + private int mAnimationMode; + + private static final int ANIMATE_IDLE = 0; + private static final int ANIMATE_IN = 1; + private static final int ANIMATE_OUT = 2; + + public ActionBarContextView(Context context) { + this(context, null); + } + + public ActionBarContextView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.actionModeStyle); + } + + public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockActionMode, defStyle, 0); + setBackgroundDrawable(a.getDrawable( + R.styleable.SherlockActionMode_background)); + mTitleStyleRes = a.getResourceId( + R.styleable.SherlockActionMode_titleTextStyle, 0); + mSubtitleStyleRes = a.getResourceId( + R.styleable.SherlockActionMode_subtitleTextStyle, 0); + + mContentHeight = a.getLayoutDimension( + R.styleable.SherlockActionMode_height, 0); + + mSplitBackground = a.getDrawable( + R.styleable.SherlockActionMode_backgroundSplit); + + a.recycle(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mActionMenuPresenter != null) { + mActionMenuPresenter.hideOverflowMenu(); + mActionMenuPresenter.hideSubMenus(); + } + } + + @Override + public void setSplitActionBar(boolean split) { + if (mSplitActionBar != split) { + if (mActionMenuPresenter != null) { + // Mode is already active; move everything over and adjust the menu itself. + final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT); + if (!split) { + mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + mMenuView.setBackgroundDrawable(null); + final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); + if (oldParent != null) oldParent.removeView(mMenuView); + addView(mMenuView, layoutParams); + } else { + // Allow full screen width in split mode. + mActionMenuPresenter.setWidthLimit( + getContext().getResources().getDisplayMetrics().widthPixels, true); + // No limit to the item count; use whatever will fit. + mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); + // Span the whole width + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = mContentHeight; + mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + mMenuView.setBackgroundDrawable(mSplitBackground); + final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); + if (oldParent != null) oldParent.removeView(mMenuView); + mSplitView.addView(mMenuView, layoutParams); + } + } + super.setSplitActionBar(split); + } + } + + public void setContentHeight(int height) { + mContentHeight = height; + } + + public void setCustomView(View view) { + if (mCustomView != null) { + removeView(mCustomView); + } + mCustomView = view; + if (mTitleLayout != null) { + removeView(mTitleLayout); + mTitleLayout = null; + } + if (view != null) { + addView(view); + } + requestLayout(); + } + + public void setTitle(CharSequence title) { + mTitle = title; + initTitle(); + } + + public void setSubtitle(CharSequence subtitle) { + mSubtitle = subtitle; + initTitle(); + } + + public CharSequence getTitle() { + return mTitle; + } + + public CharSequence getSubtitle() { + return mSubtitle; + } + + private void initTitle() { + if (mTitleLayout == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.abs__action_bar_title_item, this); + mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1); + mTitleView = (TextView) mTitleLayout.findViewById(R.id.abs__action_bar_title); + mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.abs__action_bar_subtitle); + if (mTitleStyleRes != 0) { + mTitleView.setTextAppearance(mContext, mTitleStyleRes); + } + if (mSubtitleStyleRes != 0) { + mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes); + } + } + + mTitleView.setText(mTitle); + mSubtitleView.setText(mSubtitle); + + final boolean hasTitle = !TextUtils.isEmpty(mTitle); + final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle); + mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE); + mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE); + if (mTitleLayout.getParent() == null) { + addView(mTitleLayout); + } + } + + public void initForMode(final ActionMode mode) { + if (mClose == null) { + LayoutInflater inflater = LayoutInflater.from(mContext); + mClose = (NineLinearLayout)inflater.inflate(R.layout.abs__action_mode_close_item, this, false); + addView(mClose); + } else if (mClose.getParent() == null) { + addView(mClose); + } + + View closeButton = mClose.findViewById(R.id.abs__action_mode_close_button); + closeButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mode.finish(); + } + }); + + final MenuBuilder menu = (MenuBuilder) mode.getMenu(); + if (mActionMenuPresenter != null) { + mActionMenuPresenter.dismissPopupMenus(); + } + mActionMenuPresenter = new ActionMenuPresenter(mContext); + mActionMenuPresenter.setReserveOverflow(true); + + final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT); + if (!mSplitActionBar) { + menu.addMenuPresenter(mActionMenuPresenter); + mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + mMenuView.setBackgroundDrawable(null); + addView(mMenuView, layoutParams); + } else { + // Allow full screen width in split mode. + mActionMenuPresenter.setWidthLimit( + getContext().getResources().getDisplayMetrics().widthPixels, true); + // No limit to the item count; use whatever will fit. + mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); + // Span the whole width + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = mContentHeight; + menu.addMenuPresenter(mActionMenuPresenter); + mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + mMenuView.setBackgroundDrawable(mSplitBackground); + mSplitView.addView(mMenuView, layoutParams); + } + + mAnimateInOnLayout = true; + } + + public void closeMode() { + if (mAnimationMode == ANIMATE_OUT) { + // Called again during close; just finish what we were doing. + return; + } + if (mClose == null) { + killMode(); + return; + } + + finishAnimation(); + mAnimationMode = ANIMATE_OUT; + mCurrentAnimation = makeOutAnimation(); + mCurrentAnimation.start(); + } + + private void finishAnimation() { + final Animator a = mCurrentAnimation; + if (a != null) { + mCurrentAnimation = null; + a.end(); + } + } + + public void killMode() { + finishAnimation(); + removeAllViews(); + if (mSplitView != null) { + mSplitView.removeView(mMenuView); + } + mCustomView = null; + mMenuView = null; + mAnimateInOnLayout = false; + } + + @Override + public boolean showOverflowMenu() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.showOverflowMenu(); + } + return false; + } + + @Override + public boolean hideOverflowMenu() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.hideOverflowMenu(); + } + return false; + } + + @Override + public boolean isOverflowMenuShowing() { + if (mActionMenuPresenter != null) { + return mActionMenuPresenter.isOverflowMenuShowing(); + } + return false; + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + // Used by custom views if they don't supply layout params. Everything else + // added to an ActionBarContextView should have them already. + return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + if (widthMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + + "with android:layout_width=\"match_parent\" (or fill_parent)"); + } + + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + if (heightMode == MeasureSpec.UNSPECIFIED) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + + "with android:layout_height=\"wrap_content\""); + } + + final int contentWidth = MeasureSpec.getSize(widthMeasureSpec); + + int maxHeight = mContentHeight > 0 ? + mContentHeight : MeasureSpec.getSize(heightMeasureSpec); + + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); + final int height = maxHeight - verticalPadding; + final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); + + if (mClose != null) { + availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); + MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); + availableWidth -= lp.leftMargin + lp.rightMargin; + } + + if (mMenuView != null && mMenuView.getParent() == this) { + availableWidth = measureChildView(mMenuView, availableWidth, + childSpecHeight, 0); + } + + if (mTitleLayout != null && mCustomView == null) { + availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0); + } + + if (mCustomView != null) { + ViewGroup.LayoutParams lp = mCustomView.getLayoutParams(); + final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ? + MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; + final int customWidth = lp.width >= 0 ? + Math.min(lp.width, availableWidth) : availableWidth; + final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? + MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; + final int customHeight = lp.height >= 0 ? + Math.min(lp.height, height) : height; + mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode), + MeasureSpec.makeMeasureSpec(customHeight, customHeightMode)); + } + + if (mContentHeight <= 0) { + int measuredHeight = 0; + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View v = getChildAt(i); + int paddedViewHeight = v.getMeasuredHeight() + verticalPadding; + if (paddedViewHeight > measuredHeight) { + measuredHeight = paddedViewHeight; + } + } + setMeasuredDimension(contentWidth, measuredHeight); + } else { + setMeasuredDimension(contentWidth, maxHeight); + } + } + + private Animator makeInAnimation() { + mClose.setTranslationX(-mClose.getWidth() - + ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); + ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0); + buttonAnimator.setDuration(200); + buttonAnimator.addListener(this); + buttonAnimator.setInterpolator(new DecelerateInterpolator()); + + AnimatorSet set = new AnimatorSet(); + AnimatorSet.Builder b = set.play(buttonAnimator); + + if (mMenuView != null) { + final int count = mMenuView.getChildCount(); + if (count > 0) { + for (int i = count - 1, j = 0; i >= 0; i--, j++) { + AnimatorProxy child = AnimatorProxy.wrap(mMenuView.getChildAt(i)); + child.setScaleY(0); + ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1); + a.setDuration(100); + a.setStartDelay(j * 70); + b.with(a); + } + } + } + + return set; + } + + private Animator makeOutAnimation() { + ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", + -mClose.getWidth() - ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); + buttonAnimator.setDuration(200); + buttonAnimator.addListener(this); + buttonAnimator.setInterpolator(new DecelerateInterpolator()); + + AnimatorSet set = new AnimatorSet(); + AnimatorSet.Builder b = set.play(buttonAnimator); + + if (mMenuView != null) { + final int count = mMenuView.getChildCount(); + if (count > 0) { + for (int i = 0; i < 0; i++) { + AnimatorProxy child = AnimatorProxy.wrap(mMenuView.getChildAt(i)); + child.setScaleY(0); + ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0); + a.setDuration(100); + a.setStartDelay(i * 70); + b.with(a); + } + } + } + + return set; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int x = getPaddingLeft(); + final int y = getPaddingTop(); + final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); + + if (mClose != null && mClose.getVisibility() != GONE) { + MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); + x += lp.leftMargin; + x += positionChild(mClose, x, y, contentHeight); + x += lp.rightMargin; + + if (mAnimateInOnLayout) { + mAnimationMode = ANIMATE_IN; + mCurrentAnimation = makeInAnimation(); + mCurrentAnimation.start(); + mAnimateInOnLayout = false; + } + } + + if (mTitleLayout != null && mCustomView == null) { + x += positionChild(mTitleLayout, x, y, contentHeight); + } + + if (mCustomView != null) { + x += positionChild(mCustomView, x, y, contentHeight); + } + + x = r - l - getPaddingRight(); + + if (mMenuView != null) { + x -= positionChildInverse(mMenuView, x, y, contentHeight); + } + } + + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mAnimationMode == ANIMATE_OUT) { + killMode(); + } + mAnimationMode = ANIMATE_IDLE; + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + // Action mode started + //TODO event.setSource(this); + event.setClassName(getClass().getName()); + event.setPackageName(getContext().getPackageName()); + event.setContentDescription(mTitle); + } else { + //TODO super.onInitializeAccessibilityEvent(event); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarView.java new file mode 100644 index 000000000..ae6035892 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ActionBarView.java @@ -0,0 +1,1548 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.widget; + +import org.xmlpull.v1.XmlPullParser; +import android.app.Activity; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.ActionBar.OnNavigationListener; +import com.actionbarsherlock.internal.ActionBarSherlockCompat; +import com.actionbarsherlock.internal.view.menu.ActionMenuItem; +import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter; +import com.actionbarsherlock.internal.view.menu.ActionMenuView; +import com.actionbarsherlock.internal.view.menu.MenuBuilder; +import com.actionbarsherlock.internal.view.menu.MenuItemImpl; +import com.actionbarsherlock.internal.view.menu.MenuPresenter; +import com.actionbarsherlock.internal.view.menu.MenuView; +import com.actionbarsherlock.internal.view.menu.SubMenuBuilder; +import com.actionbarsherlock.view.CollapsibleActionView; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.Window; + +import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean; + +/** + * @hide + */ +public class ActionBarView extends AbsActionBarView { + private static final String TAG = "ActionBarView"; + private static final boolean DEBUG = false; + + /** + * Display options applied by default + */ + public static final int DISPLAY_DEFAULT = 0; + + /** + * Display options that require re-layout as opposed to a simple invalidate + */ + private static final int DISPLAY_RELAYOUT_MASK = + ActionBar.DISPLAY_SHOW_HOME | + ActionBar.DISPLAY_USE_LOGO | + ActionBar.DISPLAY_HOME_AS_UP | + ActionBar.DISPLAY_SHOW_CUSTOM | + ActionBar.DISPLAY_SHOW_TITLE; + + private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL; + + private int mNavigationMode; + private int mDisplayOptions = -1; + private CharSequence mTitle; + private CharSequence mSubtitle; + private Drawable mIcon; + private Drawable mLogo; + + private HomeView mHomeLayout; + private HomeView mExpandedHomeLayout; + private LinearLayout mTitleLayout; + private TextView mTitleView; + private TextView mSubtitleView; + private View mTitleUpView; + + private IcsSpinner mSpinner; + private IcsLinearLayout mListNavLayout; + private ScrollingTabContainerView mTabScrollView; + private View mCustomNavView; + private IcsProgressBar mProgressView; + private IcsProgressBar mIndeterminateProgressView; + + private int mProgressBarPadding; + private int mItemPadding; + + private int mTitleStyleRes; + private int mSubtitleStyleRes; + private int mProgressStyle; + private int mIndeterminateProgressStyle; + + private boolean mUserTitle; + private boolean mIncludeTabs; + private boolean mIsCollapsable; + private boolean mIsCollapsed; + + private MenuBuilder mOptionsMenu; + + private ActionBarContextView mContextView; + + private ActionMenuItem mLogoNavItem; + + private SpinnerAdapter mSpinnerAdapter; + private OnNavigationListener mCallback; + + private Runnable mTabSelector; + + private ExpandedActionViewMenuPresenter mExpandedMenuPresenter; + View mExpandedActionView; + + Window.Callback mWindowCallback; + + @SuppressWarnings("rawtypes") + private final IcsAdapterView.OnItemSelectedListener mNavItemSelectedListener = + new IcsAdapterView.OnItemSelectedListener() { + public void onItemSelected(IcsAdapterView parent, View view, int position, long id) { + if (mCallback != null) { + mCallback.onNavigationItemSelected(position, id); + } + } + public void onNothingSelected(IcsAdapterView parent) { + // Do nothing + } + }; + + private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() { + @Override + public void onClick(View v) { + final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem; + if (item != null) { + item.collapseActionView(); + } + } + }; + + private final OnClickListener mUpClickListener = new OnClickListener() { + public void onClick(View v) { + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem); + } + }; + + public ActionBarView(Context context, AttributeSet attrs) { + super(context, attrs); + + // Background is always provided by the container. + setBackgroundResource(0); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockActionBar, + R.attr.actionBarStyle, 0); + + ApplicationInfo appInfo = context.getApplicationInfo(); + PackageManager pm = context.getPackageManager(); + mNavigationMode = a.getInt(R.styleable.SherlockActionBar_navigationMode, + ActionBar.NAVIGATION_MODE_STANDARD); + mTitle = a.getText(R.styleable.SherlockActionBar_title); + mSubtitle = a.getText(R.styleable.SherlockActionBar_subtitle); + + mLogo = a.getDrawable(R.styleable.SherlockActionBar_logo); + if (mLogo == null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + if (context instanceof Activity) { + //Even though native methods existed in API 9 and 10 they don't work + //so just parse the manifest to look for the logo pre-Honeycomb + final int resId = loadLogoFromManifest((Activity) context); + if (resId != 0) { + mLogo = context.getResources().getDrawable(resId); + } + } + } else { + if (context instanceof Activity) { + try { + mLogo = pm.getActivityLogo(((Activity) context).getComponentName()); + } catch (NameNotFoundException e) { + Log.e(TAG, "Activity component name not found!", e); + } + } + if (mLogo == null) { + mLogo = appInfo.loadLogo(pm); + } + } + } + + mIcon = a.getDrawable(R.styleable.SherlockActionBar_icon); + if (mIcon == null) { + if (context instanceof Activity) { + try { + mIcon = pm.getActivityIcon(((Activity) context).getComponentName()); + } catch (NameNotFoundException e) { + Log.e(TAG, "Activity component name not found!", e); + } + } + if (mIcon == null) { + mIcon = appInfo.loadIcon(pm); + } + } + + final LayoutInflater inflater = LayoutInflater.from(context); + + final int homeResId = a.getResourceId( + R.styleable.SherlockActionBar_homeLayout, + R.layout.abs__action_bar_home); + + mHomeLayout = (HomeView) inflater.inflate(homeResId, this, false); + + mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, this, false); + mExpandedHomeLayout.setUp(true); + mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener); + mExpandedHomeLayout.setContentDescription(getResources().getText( + R.string.abs__action_bar_up_description)); + + mTitleStyleRes = a.getResourceId(R.styleable.SherlockActionBar_titleTextStyle, 0); + mSubtitleStyleRes = a.getResourceId(R.styleable.SherlockActionBar_subtitleTextStyle, 0); + mProgressStyle = a.getResourceId(R.styleable.SherlockActionBar_progressBarStyle, 0); + mIndeterminateProgressStyle = a.getResourceId( + R.styleable.SherlockActionBar_indeterminateProgressStyle, 0); + + mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.SherlockActionBar_progressBarPadding, 0); + mItemPadding = a.getDimensionPixelOffset(R.styleable.SherlockActionBar_itemPadding, 0); + + setDisplayOptions(a.getInt(R.styleable.SherlockActionBar_displayOptions, DISPLAY_DEFAULT)); + + final int customNavId = a.getResourceId(R.styleable.SherlockActionBar_customNavigationLayout, 0); + if (customNavId != 0) { + mCustomNavView = (View) inflater.inflate(customNavId, this, false); + mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD; + setDisplayOptions(mDisplayOptions | ActionBar.DISPLAY_SHOW_CUSTOM); + } + + mContentHeight = a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0); + + a.recycle(); + + mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle); + mHomeLayout.setOnClickListener(mUpClickListener); + mHomeLayout.setClickable(true); + mHomeLayout.setFocusable(true); + } + + /** + * Attempt to programmatically load the logo from the manifest file of an + * activity by using an XML pull parser. This should allow us to read the + * logo attribute regardless of the platform it is being run on. + * + * @param activity Activity instance. + * @return Logo resource ID. + */ + private static int loadLogoFromManifest(Activity activity) { + int logo = 0; + try { + final String thisPackage = activity.getClass().getName(); + if (DEBUG) Log.i(TAG, "Parsing AndroidManifest.xml for " + thisPackage); + + final String packageName = activity.getApplicationInfo().packageName; + final AssetManager am = activity.createPackageContext(packageName, 0).getAssets(); + final XmlResourceParser xml = am.openXmlResourceParser("AndroidManifest.xml"); + + int eventType = xml.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String name = xml.getName(); + + if ("application".equals(name)) { + //Check if the has the attribute + if (DEBUG) Log.d(TAG, "Got "); + + for (int i = xml.getAttributeCount() - 1; i >= 0; i--) { + if (DEBUG) Log.d(TAG, xml.getAttributeName(i) + ": " + xml.getAttributeValue(i)); + + if ("logo".equals(xml.getAttributeName(i))) { + logo = xml.getAttributeResourceValue(i, 0); + break; //out of for loop + } + } + } else if ("activity".equals(name)) { + //Check if the is us and has the attribute + if (DEBUG) Log.d(TAG, "Got "); + Integer activityLogo = null; + String activityPackage = null; + boolean isOurActivity = false; + + for (int i = xml.getAttributeCount() - 1; i >= 0; i--) { + if (DEBUG) Log.d(TAG, xml.getAttributeName(i) + ": " + xml.getAttributeValue(i)); + + //We need both uiOptions and name attributes + String attrName = xml.getAttributeName(i); + if ("logo".equals(attrName)) { + activityLogo = xml.getAttributeResourceValue(i, 0); + } else if ("name".equals(attrName)) { + activityPackage = ActionBarSherlockCompat.cleanActivityName(packageName, xml.getAttributeValue(i)); + if (!thisPackage.equals(activityPackage)) { + break; //on to the next + } + isOurActivity = true; + } + + //Make sure we have both attributes before processing + if ((activityLogo != null) && (activityPackage != null)) { + //Our activity, logo specified, override with our value + logo = activityLogo.intValue(); + } + } + if (isOurActivity) { + //If we matched our activity but it had no logo don't + //do any more processing of the manifest + break; + } + } + } + eventType = xml.nextToken(); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (DEBUG) Log.i(TAG, "Returning " + Integer.toHexString(logo)); + return logo; + } + + /* + * Must be public so we can dispatch pre-2.2 via ActionBarImpl. + */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + mTitleView = null; + mSubtitleView = null; + mTitleUpView = null; + if (mTitleLayout != null && mTitleLayout.getParent() == this) { + removeView(mTitleLayout); + } + mTitleLayout = null; + if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + initTitle(); + } + + if (mTabScrollView != null && mIncludeTabs) { + ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams(); + if (lp != null) { + lp.width = LayoutParams.WRAP_CONTENT; + lp.height = LayoutParams.MATCH_PARENT; + } + mTabScrollView.setAllowCollapse(true); + } + } + + /** + * Set the window callback used to invoke menu items; used for dispatching home button presses. + * @param cb Window callback to dispatch to + */ + public void setWindowCallback(Window.Callback cb) { + mWindowCallback = cb; + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(mTabSelector); + if (mActionMenuPresenter != null) { + mActionMenuPresenter.hideOverflowMenu(); + mActionMenuPresenter.hideSubMenus(); + } + } + + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + public void initProgress() { + mProgressView = new IcsProgressBar(mContext, null, 0, mProgressStyle); + mProgressView.setId(R.id.abs__progress_horizontal); + mProgressView.setMax(10000); + addView(mProgressView); + } + + public void initIndeterminateProgress() { + mIndeterminateProgressView = new IcsProgressBar(mContext, null, 0, mIndeterminateProgressStyle); + mIndeterminateProgressView.setId(R.id.abs__progress_circular); + addView(mIndeterminateProgressView); + } + + @Override + public void setSplitActionBar(boolean splitActionBar) { + if (mSplitActionBar != splitActionBar) { + if (mMenuView != null) { + final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); + if (oldParent != null) { + oldParent.removeView(mMenuView); + } + if (splitActionBar) { + if (mSplitView != null) { + mSplitView.addView(mMenuView); + } + } else { + addView(mMenuView); + } + } + if (mSplitView != null) { + mSplitView.setVisibility(splitActionBar ? VISIBLE : GONE); + } + super.setSplitActionBar(splitActionBar); + } + } + + public boolean isSplitActionBar() { + return mSplitActionBar; + } + + public boolean hasEmbeddedTabs() { + return mIncludeTabs; + } + + public void setEmbeddedTabView(ScrollingTabContainerView tabs) { + if (mTabScrollView != null) { + removeView(mTabScrollView); + } + mTabScrollView = tabs; + mIncludeTabs = tabs != null; + if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) { + addView(mTabScrollView); + ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams(); + lp.width = LayoutParams.WRAP_CONTENT; + lp.height = LayoutParams.MATCH_PARENT; + tabs.setAllowCollapse(true); + } + } + + public void setCallback(OnNavigationListener callback) { + mCallback = callback; + } + + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + if (menu == mOptionsMenu) return; + + if (mOptionsMenu != null) { + mOptionsMenu.removeMenuPresenter(mActionMenuPresenter); + mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter); + } + + MenuBuilder builder = (MenuBuilder) menu; + mOptionsMenu = builder; + if (mMenuView != null) { + final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); + if (oldParent != null) { + oldParent.removeView(mMenuView); + } + } + if (mActionMenuPresenter == null) { + mActionMenuPresenter = new ActionMenuPresenter(mContext); + mActionMenuPresenter.setCallback(cb); + mActionMenuPresenter.setId(R.id.abs__action_menu_presenter); + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + + ActionMenuView menuView; + final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT); + if (!mSplitActionBar) { + mActionMenuPresenter.setExpandedActionViewsExclusive( + getResources_getBoolean(getContext(), + R.bool.abs__action_bar_expanded_action_views_exclusive)); + configPresenters(builder); + menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + final ViewGroup oldParent = (ViewGroup) menuView.getParent(); + if (oldParent != null && oldParent != this) { + oldParent.removeView(menuView); + } + addView(menuView, layoutParams); + } else { + mActionMenuPresenter.setExpandedActionViewsExclusive(false); + // Allow full screen width in split mode. + mActionMenuPresenter.setWidthLimit( + getContext().getResources().getDisplayMetrics().widthPixels, true); + // No limit to the item count; use whatever will fit. + mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); + // Span the whole width + layoutParams.width = LayoutParams.MATCH_PARENT; + configPresenters(builder); + menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + if (mSplitView != null) { + final ViewGroup oldParent = (ViewGroup) menuView.getParent(); + if (oldParent != null && oldParent != mSplitView) { + oldParent.removeView(menuView); + } + menuView.setVisibility(getAnimatedVisibility()); + mSplitView.addView(menuView, layoutParams); + } else { + // We'll add this later if we missed it this time. + menuView.setLayoutParams(layoutParams); + } + } + mMenuView = menuView; + } + + private void configPresenters(MenuBuilder builder) { + if (builder != null) { + builder.addMenuPresenter(mActionMenuPresenter); + builder.addMenuPresenter(mExpandedMenuPresenter); + } else { + mActionMenuPresenter.initForMenu(mContext, null); + mExpandedMenuPresenter.initForMenu(mContext, null); + mActionMenuPresenter.updateMenuView(true); + mExpandedMenuPresenter.updateMenuView(true); + } + } + + public boolean hasExpandedActionView() { + return mExpandedMenuPresenter != null && + mExpandedMenuPresenter.mCurrentExpandedItem != null; + } + + public void collapseActionView() { + final MenuItemImpl item = mExpandedMenuPresenter == null ? null : + mExpandedMenuPresenter.mCurrentExpandedItem; + if (item != null) { + item.collapseActionView(); + } + } + + public void setCustomNavigationView(View view) { + final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0; + if (mCustomNavView != null && showCustom) { + removeView(mCustomNavView); + } + mCustomNavView = view; + if (mCustomNavView != null && showCustom) { + addView(mCustomNavView); + } + } + + public CharSequence getTitle() { + return mTitle; + } + + /** + * Set the action bar title. This will always replace or override window titles. + * @param title Title to set + * + * @see #setWindowTitle(CharSequence) + */ + public void setTitle(CharSequence title) { + mUserTitle = true; + setTitleImpl(title); + } + + /** + * Set the window title. A window title will always be replaced or overridden by a user title. + * @param title Title to set + * + * @see #setTitle(CharSequence) + */ + public void setWindowTitle(CharSequence title) { + if (!mUserTitle) { + setTitleImpl(title); + } + } + + private void setTitleImpl(CharSequence title) { + mTitle = title; + if (mTitleView != null) { + mTitleView.setText(title); + final boolean visible = mExpandedActionView == null && + (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0 && + (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle)); + mTitleLayout.setVisibility(visible ? VISIBLE : GONE); + } + if (mLogoNavItem != null) { + mLogoNavItem.setTitle(title); + } + } + + public CharSequence getSubtitle() { + return mSubtitle; + } + + public void setSubtitle(CharSequence subtitle) { + mSubtitle = subtitle; + if (mSubtitleView != null) { + mSubtitleView.setText(subtitle); + mSubtitleView.setVisibility(subtitle != null ? VISIBLE : GONE); + final boolean visible = mExpandedActionView == null && + (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0 && + (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle)); + mTitleLayout.setVisibility(visible ? VISIBLE : GONE); + } + } + + public void setHomeButtonEnabled(boolean enable) { + mHomeLayout.setEnabled(enable); + mHomeLayout.setFocusable(enable); + // Make sure the home button has an accurate content description for accessibility. + if (!enable) { + mHomeLayout.setContentDescription(null); + } else if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mHomeLayout.setContentDescription(mContext.getResources().getText( + R.string.abs__action_bar_up_description)); + } else { + mHomeLayout.setContentDescription(mContext.getResources().getText( + R.string.abs__action_bar_home_description)); + } + } + + public void setDisplayOptions(int options) { + final int flagsChanged = mDisplayOptions == -1 ? -1 : options ^ mDisplayOptions; + mDisplayOptions = options; + + if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) { + final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0; + final int vis = showHome && mExpandedActionView == null ? VISIBLE : GONE; + mHomeLayout.setVisibility(vis); + + if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + final boolean setUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0; + mHomeLayout.setUp(setUp); + + // Showing home as up implicitly enables interaction with it. + // In honeycomb it was always enabled, so make this transition + // a bit easier for developers in the common case. + // (It would be silly to show it as up without responding to it.) + if (setUp) { + setHomeButtonEnabled(true); + } + } + + if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) { + final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0; + mHomeLayout.setIcon(logoVis ? mLogo : mIcon); + } + + if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + if ((options & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + initTitle(); + } else { + removeView(mTitleLayout); + } + } + + if (mTitleLayout != null && (flagsChanged & + (ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME)) != 0) { + final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0; + mTitleUpView.setVisibility(!showHome ? (homeAsUp ? VISIBLE : INVISIBLE) : GONE); + mTitleLayout.setEnabled(!showHome && homeAsUp); + } + + if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) { + if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + addView(mCustomNavView); + } else { + removeView(mCustomNavView); + } + } + + requestLayout(); + } else { + invalidate(); + } + + // Make sure the home button has an accurate content description for accessibility. + if (!mHomeLayout.isEnabled()) { + mHomeLayout.setContentDescription(null); + } else if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mHomeLayout.setContentDescription(mContext.getResources().getText( + R.string.abs__action_bar_up_description)); + } else { + mHomeLayout.setContentDescription(mContext.getResources().getText( + R.string.abs__action_bar_home_description)); + } + } + + public void setIcon(Drawable icon) { + mIcon = icon; + if (icon != null && + ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) { + mHomeLayout.setIcon(icon); + } + } + + public void setIcon(int resId) { + setIcon(mContext.getResources().getDrawable(resId)); + } + + public void setLogo(Drawable logo) { + mLogo = logo; + if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) { + mHomeLayout.setIcon(logo); + } + } + + public void setLogo(int resId) { + setLogo(mContext.getResources().getDrawable(resId)); + } + + public void setNavigationMode(int mode) { + final int oldMode = mNavigationMode; + if (mode != oldMode) { + switch (oldMode) { + case ActionBar.NAVIGATION_MODE_LIST: + if (mListNavLayout != null) { + removeView(mListNavLayout); + } + break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabScrollView != null && mIncludeTabs) { + removeView(mTabScrollView); + } + } + + switch (mode) { + case ActionBar.NAVIGATION_MODE_LIST: + if (mSpinner == null) { + mSpinner = new IcsSpinner(mContext, null, + R.attr.actionDropDownStyle); + mListNavLayout = (IcsLinearLayout) LayoutInflater.from(mContext) + .inflate(R.layout.abs__action_bar_tab_bar_view, null); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); + params.gravity = Gravity.CENTER; + mListNavLayout.addView(mSpinner, params); + } + if (mSpinner.getAdapter() != mSpinnerAdapter) { + mSpinner.setAdapter(mSpinnerAdapter); + } + mSpinner.setOnItemSelectedListener(mNavItemSelectedListener); + addView(mListNavLayout); + break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabScrollView != null && mIncludeTabs) { + addView(mTabScrollView); + } + break; + } + mNavigationMode = mode; + requestLayout(); + } + } + + public void setDropdownAdapter(SpinnerAdapter adapter) { + mSpinnerAdapter = adapter; + if (mSpinner != null) { + mSpinner.setAdapter(adapter); + } + } + + public SpinnerAdapter getDropdownAdapter() { + return mSpinnerAdapter; + } + + public void setDropdownSelectedPosition(int position) { + mSpinner.setSelection(position); + } + + public int getDropdownSelectedPosition() { + return mSpinner.getSelectedItemPosition(); + } + + public View getCustomNavigationView() { + return mCustomNavView; + } + + public int getNavigationMode() { + return mNavigationMode; + } + + public int getDisplayOptions() { + return mDisplayOptions; + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + // Used by custom nav views if they don't supply layout params. Everything else + // added to an ActionBarView should have them already. + return new ActionBar.LayoutParams(DEFAULT_CUSTOM_GRAVITY); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + addView(mHomeLayout); + + if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + final ViewParent parent = mCustomNavView.getParent(); + if (parent != this) { + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(mCustomNavView); + } + addView(mCustomNavView); + } + } + } + + private void initTitle() { + if (mTitleLayout == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + mTitleLayout = (LinearLayout) inflater.inflate(R.layout.abs__action_bar_title_item, + this, false); + mTitleView = (TextView) mTitleLayout.findViewById(R.id.abs__action_bar_title); + mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.abs__action_bar_subtitle); + mTitleUpView = (View) mTitleLayout.findViewById(R.id.abs__up); + + mTitleLayout.setOnClickListener(mUpClickListener); + + if (mTitleStyleRes != 0) { + mTitleView.setTextAppearance(mContext, mTitleStyleRes); + } + if (mTitle != null) { + mTitleView.setText(mTitle); + } + + if (mSubtitleStyleRes != 0) { + mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes); + } + if (mSubtitle != null) { + mSubtitleView.setText(mSubtitle); + mSubtitleView.setVisibility(VISIBLE); + } + + final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0; + final boolean showHome = (mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0; + mTitleUpView.setVisibility(!showHome ? (homeAsUp ? VISIBLE : INVISIBLE) : GONE); + mTitleLayout.setEnabled(homeAsUp && !showHome); + } + + addView(mTitleLayout); + if (mExpandedActionView != null || + (TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle))) { + // Don't show while in expanded mode or with empty text + mTitleLayout.setVisibility(GONE); + } + } + + public void setContextView(ActionBarContextView view) { + mContextView = view; + } + + public void setCollapsable(boolean collapsable) { + mIsCollapsable = collapsable; + } + + public boolean isCollapsed() { + return mIsCollapsed; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int childCount = getChildCount(); + if (mIsCollapsable) { + int visibleChildren = 0; + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE && + !(child == mMenuView && mMenuView.getChildCount() == 0)) { + visibleChildren++; + } + } + + if (visibleChildren == 0) { + // No size for an empty action bar when collapsable. + setMeasuredDimension(0, 0); + mIsCollapsed = true; + return; + } + } + mIsCollapsed = false; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + if (widthMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + + "with android:layout_width=\"match_parent\" (or fill_parent)"); + } + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + if (heightMode != MeasureSpec.AT_MOST) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + + "with android:layout_height=\"wrap_content\""); + } + + int contentWidth = MeasureSpec.getSize(widthMeasureSpec); + + int maxHeight = mContentHeight > 0 ? + mContentHeight : MeasureSpec.getSize(heightMeasureSpec); + + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int paddingLeft = getPaddingLeft(); + final int paddingRight = getPaddingRight(); + final int height = maxHeight - verticalPadding; + final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); + + int availableWidth = contentWidth - paddingLeft - paddingRight; + int leftOfCenter = availableWidth / 2; + int rightOfCenter = leftOfCenter; + + HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout; + + if (homeLayout.getVisibility() != GONE) { + final ViewGroup.LayoutParams lp = homeLayout.getLayoutParams(); + int homeWidthSpec; + if (lp.width < 0) { + homeWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); + } else { + homeWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); + } + homeLayout.measure(homeWidthSpec, + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + final int homeWidth = homeLayout.getMeasuredWidth() + homeLayout.getLeftOffset(); + availableWidth = Math.max(0, availableWidth - homeWidth); + leftOfCenter = Math.max(0, availableWidth - homeWidth); + } + + if (mMenuView != null && mMenuView.getParent() == this) { + availableWidth = measureChildView(mMenuView, availableWidth, + childSpecHeight, 0); + rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth()); + } + + if (mIndeterminateProgressView != null && + mIndeterminateProgressView.getVisibility() != GONE) { + availableWidth = measureChildView(mIndeterminateProgressView, availableWidth, + childSpecHeight, 0); + rightOfCenter = Math.max(0, + rightOfCenter - mIndeterminateProgressView.getMeasuredWidth()); + } + + final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE && + (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0; + + if (mExpandedActionView == null) { + switch (mNavigationMode) { + case ActionBar.NAVIGATION_MODE_LIST: + if (mListNavLayout != null) { + final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding; + availableWidth = Math.max(0, availableWidth - itemPaddingSize); + leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize); + mListNavLayout.measure( + MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + final int listNavWidth = mListNavLayout.getMeasuredWidth(); + availableWidth = Math.max(0, availableWidth - listNavWidth); + leftOfCenter = Math.max(0, leftOfCenter - listNavWidth); + } + break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabScrollView != null) { + final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding; + availableWidth = Math.max(0, availableWidth - itemPaddingSize); + leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize); + mTabScrollView.measure( + MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + final int tabWidth = mTabScrollView.getMeasuredWidth(); + availableWidth = Math.max(0, availableWidth - tabWidth); + leftOfCenter = Math.max(0, leftOfCenter - tabWidth); + } + break; + } + } + + View customView = null; + if (mExpandedActionView != null) { + customView = mExpandedActionView; + } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && + mCustomNavView != null) { + customView = mCustomNavView; + } + + if (customView != null) { + final ViewGroup.LayoutParams lp = generateLayoutParams(customView.getLayoutParams()); + final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ? + (ActionBar.LayoutParams) lp : null; + + int horizontalMargin = 0; + int verticalMargin = 0; + if (ablp != null) { + horizontalMargin = ablp.leftMargin + ablp.rightMargin; + verticalMargin = ablp.topMargin + ablp.bottomMargin; + } + + // If the action bar is wrapping to its content height, don't allow a custom + // view to MATCH_PARENT. + int customNavHeightMode; + if (mContentHeight <= 0) { + customNavHeightMode = MeasureSpec.AT_MOST; + } else { + customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? + MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; + } + final int customNavHeight = Math.max(0, + (lp.height >= 0 ? Math.min(lp.height, height) : height) - verticalMargin); + + final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ? + MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; + int customNavWidth = Math.max(0, + (lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth) + - horizontalMargin); + final int hgrav = (ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY) & + Gravity.HORIZONTAL_GRAVITY_MASK; + + // Centering a custom view is treated specially; we try to center within the whole + // action bar rather than in the available space. + if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.MATCH_PARENT) { + customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2; + } + + customView.measure( + MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode), + MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode)); + availableWidth -= horizontalMargin + customView.getMeasuredWidth(); + } + + if (mExpandedActionView == null && showTitle) { + availableWidth = measureChildView(mTitleLayout, availableWidth, + MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY), 0); + leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth()); + } + + if (mContentHeight <= 0) { + int measuredHeight = 0; + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + int paddedViewHeight = v.getMeasuredHeight() + verticalPadding; + if (paddedViewHeight > measuredHeight) { + measuredHeight = paddedViewHeight; + } + } + setMeasuredDimension(contentWidth, measuredHeight); + } else { + setMeasuredDimension(contentWidth, maxHeight); + } + + if (mContextView != null) { + mContextView.setContentHeight(getMeasuredHeight()); + } + + if (mProgressView != null && mProgressView.getVisibility() != GONE) { + mProgressView.measure(MeasureSpec.makeMeasureSpec( + contentWidth - mProgressBarPadding * 2, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST)); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int x = getPaddingLeft(); + final int y = getPaddingTop(); + final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); + + if (contentHeight <= 0) { + // Nothing to do if we can't see anything. + return; + } + + HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout; + if (homeLayout.getVisibility() != GONE) { + final int leftOffset = homeLayout.getLeftOffset(); + x += positionChild(homeLayout, x + leftOffset, y, contentHeight) + leftOffset; + } + + if (mExpandedActionView == null) { + final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE && + (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0; + if (showTitle) { + x += positionChild(mTitleLayout, x, y, contentHeight); + } + + switch (mNavigationMode) { + case ActionBar.NAVIGATION_MODE_STANDARD: + break; + case ActionBar.NAVIGATION_MODE_LIST: + if (mListNavLayout != null) { + if (showTitle) x += mItemPadding; + x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding; + } + break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabScrollView != null) { + if (showTitle) x += mItemPadding; + x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding; + } + break; + } + } + + int menuLeft = r - l - getPaddingRight(); + if (mMenuView != null && mMenuView.getParent() == this) { + positionChildInverse(mMenuView, menuLeft, y, contentHeight); + menuLeft -= mMenuView.getMeasuredWidth(); + } + + if (mIndeterminateProgressView != null && + mIndeterminateProgressView.getVisibility() != GONE) { + positionChildInverse(mIndeterminateProgressView, menuLeft, y, contentHeight); + menuLeft -= mIndeterminateProgressView.getMeasuredWidth(); + } + + View customView = null; + if (mExpandedActionView != null) { + customView = mExpandedActionView; + } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && + mCustomNavView != null) { + customView = mCustomNavView; + } + if (customView != null) { + ViewGroup.LayoutParams lp = customView.getLayoutParams(); + final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ? + (ActionBar.LayoutParams) lp : null; + + final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY; + final int navWidth = customView.getMeasuredWidth(); + + int topMargin = 0; + int bottomMargin = 0; + if (ablp != null) { + x += ablp.leftMargin; + menuLeft -= ablp.rightMargin; + topMargin = ablp.topMargin; + bottomMargin = ablp.bottomMargin; + } + + int hgravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + // See if we actually have room to truly center; if not push against left or right. + if (hgravity == Gravity.CENTER_HORIZONTAL) { + final int centeredLeft = ((getRight() - getLeft()) - navWidth) / 2; + if (centeredLeft < x) { + hgravity = Gravity.LEFT; + } else if (centeredLeft + navWidth > menuLeft) { + hgravity = Gravity.RIGHT; + } + } else if (gravity == -1) { + hgravity = Gravity.LEFT; + } + + int xpos = 0; + switch (hgravity) { + case Gravity.CENTER_HORIZONTAL: + xpos = ((getRight() - getLeft()) - navWidth) / 2; + break; + case Gravity.LEFT: + xpos = x; + break; + case Gravity.RIGHT: + xpos = menuLeft - navWidth; + break; + } + + int vgravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + + if (gravity == -1) { + vgravity = Gravity.CENTER_VERTICAL; + } + + int ypos = 0; + switch (vgravity) { + case Gravity.CENTER_VERTICAL: + final int paddedTop = getPaddingTop(); + final int paddedBottom = getBottom() - getTop() - getPaddingBottom(); + ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2; + break; + case Gravity.TOP: + ypos = getPaddingTop() + topMargin; + break; + case Gravity.BOTTOM: + ypos = getHeight() - getPaddingBottom() - customView.getMeasuredHeight() + - bottomMargin; + break; + } + final int customWidth = customView.getMeasuredWidth(); + customView.layout(xpos, ypos, xpos + customWidth, + ypos + customView.getMeasuredHeight()); + x += customWidth; + } + + if (mProgressView != null) { + mProgressView.bringToFront(); + final int halfProgressHeight = mProgressView.getMeasuredHeight() / 2; + mProgressView.layout(mProgressBarPadding, -halfProgressHeight, + mProgressBarPadding + mProgressView.getMeasuredWidth(), halfProgressHeight); + } + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new ActionBar.LayoutParams(getContext(), attrs); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { + if (lp == null) { + lp = generateDefaultLayoutParams(); + } + return lp; + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState state = new SavedState(superState); + + if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) { + state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId(); + } + + state.isOverflowOpen = isOverflowMenuShowing(); + + return state; + } + + @Override + public void onRestoreInstanceState(Parcelable p) { + SavedState state = (SavedState) p; + + super.onRestoreInstanceState(state.getSuperState()); + + if (state.expandedMenuItemId != 0 && + mExpandedMenuPresenter != null && mOptionsMenu != null) { + final MenuItem item = mOptionsMenu.findItem(state.expandedMenuItemId); + if (item != null) { + item.expandActionView(); + } + } + + if (state.isOverflowOpen) { + postShowOverflowMenu(); + } + } + + static class SavedState extends BaseSavedState { + int expandedMenuItemId; + boolean isOverflowOpen; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + expandedMenuItemId = in.readInt(); + isOverflowOpen = in.readInt() != 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(expandedMenuItemId); + out.writeInt(isOverflowOpen ? 1 : 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + public static class HomeView extends FrameLayout { + private View mUpView; + private ImageView mIconView; + private int mUpWidth; + + public HomeView(Context context) { + this(context, null); + } + + public HomeView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setUp(boolean isUp) { + mUpView.setVisibility(isUp ? VISIBLE : GONE); + } + + public void setIcon(Drawable icon) { + mIconView.setImageDrawable(icon); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + onPopulateAccessibilityEvent(event); + return true; + } + + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + super.onPopulateAccessibilityEvent(event); + } + final CharSequence cdesc = getContentDescription(); + if (!TextUtils.isEmpty(cdesc)) { + event.getText().add(cdesc); + } + } + + @Override + public boolean dispatchHoverEvent(MotionEvent event) { + // Don't allow children to hover; we want this to be treated as a single component. + return onHoverEvent(event); + } + + @Override + protected void onFinishInflate() { + mUpView = findViewById(R.id.abs__up); + mIconView = (ImageView) findViewById(R.id.abs__home); + } + + public int getLeftOffset() { + return mUpView.getVisibility() == GONE ? mUpWidth : 0; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0); + final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams(); + mUpWidth = upLp.leftMargin + mUpView.getMeasuredWidth() + upLp.rightMargin; + int width = mUpView.getVisibility() == GONE ? 0 : mUpWidth; + int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin; + measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0); + final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); + width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin; + height = Math.max(height, + iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + switch (widthMode) { + case MeasureSpec.AT_MOST: + width = Math.min(width, widthSize); + break; + case MeasureSpec.EXACTLY: + width = widthSize; + break; + case MeasureSpec.UNSPECIFIED: + default: + break; + } + switch (heightMode) { + case MeasureSpec.AT_MOST: + height = Math.min(height, heightSize); + break; + case MeasureSpec.EXACTLY: + height = heightSize; + break; + case MeasureSpec.UNSPECIFIED: + default: + break; + } + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int vCenter = (b - t) / 2; + //UNUSED int width = r - l; + int upOffset = 0; + if (mUpView.getVisibility() != GONE) { + final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams(); + final int upHeight = mUpView.getMeasuredHeight(); + final int upWidth = mUpView.getMeasuredWidth(); + final int upTop = vCenter - upHeight / 2; + mUpView.layout(0, upTop, upWidth, upTop + upHeight); + upOffset = upLp.leftMargin + upWidth + upLp.rightMargin; + //UNUSED width -= upOffset; + l += upOffset; + } + final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); + final int iconHeight = mIconView.getMeasuredHeight(); + final int iconWidth = mIconView.getMeasuredWidth(); + final int hCenter = (r - l) / 2; + final int iconLeft = upOffset + Math.max(iconLp.leftMargin, hCenter - iconWidth / 2); + final int iconTop = Math.max(iconLp.topMargin, vCenter - iconHeight / 2); + mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight); + } + } + + private class ExpandedActionViewMenuPresenter implements MenuPresenter { + MenuBuilder mMenu; + MenuItemImpl mCurrentExpandedItem; + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + // Clear the expanded action view when menus change. + if (mMenu != null && mCurrentExpandedItem != null) { + mMenu.collapseItemActionView(mCurrentExpandedItem); + } + mMenu = menu; + } + + @Override + public MenuView getMenuView(ViewGroup root) { + return null; + } + + @Override + public void updateMenuView(boolean cleared) { + // Make sure the expanded item we have is still there. + if (mCurrentExpandedItem != null) { + boolean found = false; + + if (mMenu != null) { + final int count = mMenu.size(); + for (int i = 0; i < count; i++) { + final MenuItem item = mMenu.getItem(i); + if (item == mCurrentExpandedItem) { + found = true; + break; + } + } + } + + if (!found) { + // The item we had expanded disappeared. Collapse. + collapseItemActionView(mMenu, mCurrentExpandedItem); + } + } + } + + @Override + public void setCallback(Callback cb) { + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + } + + @Override + public boolean flagActionItems() { + return false; + } + + @Override + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + mExpandedActionView = item.getActionView(); + mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(/* TODO getResources() */)); + mCurrentExpandedItem = item; + if (mExpandedActionView.getParent() != ActionBarView.this) { + addView(mExpandedActionView); + } + if (mExpandedHomeLayout.getParent() != ActionBarView.this) { + addView(mExpandedHomeLayout); + } + mHomeLayout.setVisibility(GONE); + if (mTitleLayout != null) mTitleLayout.setVisibility(GONE); + if (mTabScrollView != null) mTabScrollView.setVisibility(GONE); + if (mSpinner != null) mSpinner.setVisibility(GONE); + if (mCustomNavView != null) mCustomNavView.setVisibility(GONE); + requestLayout(); + item.setActionViewExpanded(true); + + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded(); + } + + return true; + } + + @Override + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + // Do this before detaching the actionview from the hierarchy, in case + // it needs to dismiss the soft keyboard, etc. + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed(); + } + + removeView(mExpandedActionView); + removeView(mExpandedHomeLayout); + mExpandedActionView = null; + if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) { + mHomeLayout.setVisibility(VISIBLE); + } + if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + if (mTitleLayout == null) { + initTitle(); + } else { + mTitleLayout.setVisibility(VISIBLE); + } + } + if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) { + mTabScrollView.setVisibility(VISIBLE); + } + if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) { + mSpinner.setVisibility(VISIBLE); + } + if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mCustomNavView.setVisibility(VISIBLE); + } + mExpandedHomeLayout.setIcon(null); + mCurrentExpandedItem = null; + requestLayout(); + item.setActionViewExpanded(false); + + return true; + } + + @Override + public int getId() { + return 0; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingButton.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingButton.java new file mode 100644 index 000000000..fa3698f3b --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingButton.java @@ -0,0 +1,40 @@ +package com.actionbarsherlock.internal.widget; + +import java.util.Locale; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.Button; + +public class CapitalizingButton extends Button { + private static final boolean SANS_ICE_CREAM = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH; + private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + + private static final int[] R_styleable_Button = new int[] { + android.R.attr.textAllCaps + }; + private static final int R_styleable_Button_textAllCaps = 0; + + private boolean mAllCaps; + + public CapitalizingButton(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R_styleable_Button); + mAllCaps = a.getBoolean(R_styleable_Button_textAllCaps, true); + a.recycle(); + } + + public void setTextCompat(CharSequence text) { + if (SANS_ICE_CREAM && mAllCaps && text != null) { + if (IS_GINGERBREAD) { + setText(text.toString().toUpperCase(Locale.ROOT)); + } else { + setText(text.toString().toUpperCase()); + } + } else { + setText(text); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java new file mode 100644 index 000000000..1067b4191 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java @@ -0,0 +1,40 @@ +package com.actionbarsherlock.internal.widget; + +import java.util.Locale; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.TextView; + +public class CapitalizingTextView extends TextView { + private static final boolean SANS_ICE_CREAM = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH; + private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + + private static final int[] R_styleable_TextView = new int[] { + android.R.attr.textAllCaps + }; + private static final int R_styleable_TextView_textAllCaps = 0; + + private boolean mAllCaps; + + public CapitalizingTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R_styleable_TextView, defStyle, 0); + mAllCaps = a.getBoolean(R_styleable_TextView_textAllCaps, true); + a.recycle(); + } + + public void setTextCompat(CharSequence text) { + if (SANS_ICE_CREAM && mAllCaps && text != null) { + if (IS_GINGERBREAD) { + setText(text.toString().toUpperCase(Locale.ROOT)); + } else { + setText(text.toString().toUpperCase()); + } + } else { + setText(text); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/FakeDialogPhoneWindow.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/FakeDialogPhoneWindow.java new file mode 100644 index 000000000..ad1b4f0a8 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/FakeDialogPhoneWindow.java @@ -0,0 +1,64 @@ +package com.actionbarsherlock.internal.widget; + +import static android.view.View.MeasureSpec.EXACTLY; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.widget.LinearLayout; +import com.actionbarsherlock.R; + +public class FakeDialogPhoneWindow extends LinearLayout { + final TypedValue mMinWidthMajor = new TypedValue(); + final TypedValue mMinWidthMinor = new TypedValue(); + + public FakeDialogPhoneWindow(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockTheme); + + a.getValue(R.styleable.SherlockTheme_windowMinWidthMajor, mMinWidthMajor); + a.getValue(R.styleable.SherlockTheme_windowMinWidthMinor, mMinWidthMinor); + + a.recycle(); + } + + /* Stolen from PhoneWindow */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = getMeasuredWidth(); + boolean measure = false; + + widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); + + final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor; + + if (tv.type != TypedValue.TYPE_NULL) { + final int min; + if (tv.type == TypedValue.TYPE_DIMENSION) { + min = (int)tv.getDimension(metrics); + } else if (tv.type == TypedValue.TYPE_FRACTION) { + min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); + } else { + min = 0; + } + + if (width < min) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); + measure = true; + } + } + + // TODO: Support height? + + if (measure) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAbsSpinner.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAbsSpinner.java new file mode 100644 index 000000000..ce0cb3bca --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAbsSpinner.java @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SpinnerAdapter; + +/** + * An abstract base class for spinner widgets. SDK users will probably not + * need to use this class. + * + * @attr ref android.R.styleable#AbsSpinner_entries + */ +public abstract class IcsAbsSpinner extends IcsAdapterView { + private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + + SpinnerAdapter mAdapter; + + int mHeightMeasureSpec; + int mWidthMeasureSpec; + boolean mBlockLayoutRequests; + + int mSelectionLeftPadding = 0; + int mSelectionTopPadding = 0; + int mSelectionRightPadding = 0; + int mSelectionBottomPadding = 0; + final Rect mSpinnerPadding = new Rect(); + + final RecycleBin mRecycler = new RecycleBin(); + private DataSetObserver mDataSetObserver; + + /** Temporary frame to hold a child View's frame rectangle */ + private Rect mTouchFrame; + + public IcsAbsSpinner(Context context) { + super(context); + initAbsSpinner(); + } + + public IcsAbsSpinner(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public IcsAbsSpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initAbsSpinner(); + + /* + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.AbsSpinner, defStyle, 0); + + CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries); + if (entries != null) { + ArrayAdapter adapter = + new ArrayAdapter(context, + R.layout.simple_spinner_item, entries); + adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); + setAdapter(adapter); + } + + a.recycle(); + */ + } + + /** + * Common code for different constructor flavors + */ + private void initAbsSpinner() { + setFocusable(true); + setWillNotDraw(false); + } + + /** + * The Adapter is used to provide the data which backs this Spinner. + * It also provides methods to transform spinner items based on their position + * relative to the selected item. + * @param adapter The SpinnerAdapter to use for this Spinner + */ + @Override + public void setAdapter(SpinnerAdapter adapter) { + if (null != mAdapter) { + mAdapter.unregisterDataSetObserver(mDataSetObserver); + resetList(); + } + + mAdapter = adapter; + + mOldSelectedPosition = INVALID_POSITION; + mOldSelectedRowId = INVALID_ROW_ID; + + if (mAdapter != null) { + mOldItemCount = mItemCount; + mItemCount = mAdapter.getCount(); + checkFocus(); + + mDataSetObserver = new AdapterDataSetObserver(); + mAdapter.registerDataSetObserver(mDataSetObserver); + + int position = mItemCount > 0 ? 0 : INVALID_POSITION; + + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + + if (mItemCount == 0) { + // Nothing selected + checkSelectionChanged(); + } + + } else { + checkFocus(); + resetList(); + // Nothing selected + checkSelectionChanged(); + } + + requestLayout(); + } + + /** + * Clear out all children from the list + */ + void resetList() { + mDataChanged = false; + mNeedSync = false; + + removeAllViewsInLayout(); + mOldSelectedPosition = INVALID_POSITION; + mOldSelectedRowId = INVALID_ROW_ID; + + setSelectedPositionInt(INVALID_POSITION); + setNextSelectedPositionInt(INVALID_POSITION); + invalidate(); + } + + /** + * @see android.view.View#measure(int, int) + * + * Figure out the dimensions of this Spinner. The width comes from + * the widthMeasureSpec as Spinnners can't have their width set to + * UNSPECIFIED. The height is based on the height of the selected item + * plus padding. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize; + int heightSize; + + final int mPaddingLeft = getPaddingLeft(); + final int mPaddingTop = getPaddingTop(); + final int mPaddingRight = getPaddingRight(); + final int mPaddingBottom = getPaddingBottom(); + + mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft + : mSelectionLeftPadding; + mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop + : mSelectionTopPadding; + mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight + : mSelectionRightPadding; + mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom + : mSelectionBottomPadding; + + if (mDataChanged) { + handleDataChanged(); + } + + int preferredHeight = 0; + int preferredWidth = 0; + boolean needsMeasuring = true; + + int selectedPosition = getSelectedItemPosition(); + if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) { + // Try looking in the recycler. (Maybe we were measured once already) + View view = mRecycler.get(selectedPosition); + if (view == null) { + // Make a new one + view = mAdapter.getView(selectedPosition, null, this); + } + + if (view != null) { + // Put in recycler for re-measuring and/or layout + mRecycler.put(selectedPosition, view); + } + + if (view != null) { + if (view.getLayoutParams() == null) { + mBlockLayoutRequests = true; + view.setLayoutParams(generateDefaultLayoutParams()); + mBlockLayoutRequests = false; + } + measureChild(view, widthMeasureSpec, heightMeasureSpec); + + preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom; + preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right; + + needsMeasuring = false; + } + } + + if (needsMeasuring) { + // No views -- just use padding + preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom; + if (widthMode == MeasureSpec.UNSPECIFIED) { + preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right; + } + } + + preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight()); + preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth()); + + if (IS_HONEYCOMB) { + heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0); + widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0); + } else { + heightSize = resolveSize(preferredHeight, heightMeasureSpec); + widthSize = resolveSize(preferredWidth, widthMeasureSpec); + } + + setMeasuredDimension(widthSize, heightSize); + mHeightMeasureSpec = heightMeasureSpec; + mWidthMeasureSpec = widthMeasureSpec; + } + + int getChildHeight(View child) { + return child.getMeasuredHeight(); + } + + int getChildWidth(View child) { + return child.getMeasuredWidth(); + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + + void recycleAllViews() { + final int childCount = getChildCount(); + final IcsAbsSpinner.RecycleBin recycleBin = mRecycler; + final int position = mFirstPosition; + + // All views go in recycler + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + int index = position + i; + recycleBin.put(index, v); + } + } + + /** + * Jump directly to a specific item in the adapter data. + */ + public void setSelection(int position, boolean animate) { + // Animate only if requested position is already on screen somewhere + boolean shouldAnimate = animate && mFirstPosition <= position && + position <= mFirstPosition + getChildCount() - 1; + setSelectionInt(position, shouldAnimate); + } + + @Override + public void setSelection(int position) { + setNextSelectedPositionInt(position); + requestLayout(); + invalidate(); + } + + + /** + * Makes the item at the supplied position selected. + * + * @param position Position to select + * @param animate Should the transition be animated + * + */ + void setSelectionInt(int position, boolean animate) { + if (position != mOldSelectedPosition) { + mBlockLayoutRequests = true; + int delta = position - mSelectedPosition; + setNextSelectedPositionInt(position); + layout(delta, animate); + mBlockLayoutRequests = false; + } + } + + abstract void layout(int delta, boolean animate); + + @Override + public View getSelectedView() { + if (mItemCount > 0 && mSelectedPosition >= 0) { + return getChildAt(mSelectedPosition - mFirstPosition); + } else { + return null; + } + } + + /** + * Override to prevent spamming ourselves with layout requests + * as we place views + * + * @see android.view.View#requestLayout() + */ + @Override + public void requestLayout() { + if (!mBlockLayoutRequests) { + super.requestLayout(); + } + } + + @Override + public SpinnerAdapter getAdapter() { + return mAdapter; + } + + @Override + public int getCount() { + return mItemCount; + } + + /** + * Maps a point to a position in the list. + * + * @param x X in local coordinate + * @param y Y in local coordinate + * @return The position of the item which contains the specified point, or + * {@link #INVALID_POSITION} if the point does not intersect an item. + */ + public int pointToPosition(int x, int y) { + Rect frame = mTouchFrame; + if (frame == null) { + mTouchFrame = new Rect(); + frame = mTouchFrame; + } + + final int count = getChildCount(); + for (int i = count - 1; i >= 0; i--) { + View child = getChildAt(i); + if (child.getVisibility() == View.VISIBLE) { + child.getHitRect(frame); + if (frame.contains(x, y)) { + return mFirstPosition + i; + } + } + } + return INVALID_POSITION; + } + + static class SavedState extends BaseSavedState { + long selectedId; + int position; + + /** + * Constructor called from {@link AbsSpinner#onSaveInstanceState()} + */ + SavedState(Parcelable superState) { + super(superState); + } + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(Parcel in) { + super(in); + selectedId = in.readLong(); + position = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeLong(selectedId); + out.writeInt(position); + } + + @Override + public String toString() { + return "AbsSpinner.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " selectedId=" + selectedId + + " position=" + position + "}"; + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.selectedId = getSelectedItemId(); + if (ss.selectedId >= 0) { + ss.position = getSelectedItemPosition(); + } else { + ss.position = INVALID_POSITION; + } + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + + super.onRestoreInstanceState(ss.getSuperState()); + + if (ss.selectedId >= 0) { + mDataChanged = true; + mNeedSync = true; + mSyncRowId = ss.selectedId; + mSyncPosition = ss.position; + mSyncMode = SYNC_SELECTED_POSITION; + requestLayout(); + } + } + + class RecycleBin { + private final SparseArray mScrapHeap = new SparseArray(); + + public void put(int position, View v) { + mScrapHeap.put(position, v); + } + + View get(int position) { + // System.out.print("Looking for " + position); + View result = mScrapHeap.get(position); + if (result != null) { + // System.out.println(" HIT"); + mScrapHeap.delete(position); + } else { + // System.out.println(" MISS"); + } + return result; + } + + void clear() { + final SparseArray scrapHeap = mScrapHeap; + final int count = scrapHeap.size(); + for (int i = 0; i < count; i++) { + final View view = scrapHeap.valueAt(i); + if (view != null) { + removeDetachedView(view, true); + } + } + scrapHeap.clear(); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAdapterView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAdapterView.java new file mode 100644 index 000000000..c786dc5c1 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsAdapterView.java @@ -0,0 +1,1160 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.database.DataSetObserver; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.ContextMenu; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Adapter; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + + +/** + * An AdapterView is a view whose children are determined by an {@link Adapter}. + * + *

+ * See {@link ListView}, {@link GridView}, {@link Spinner} and + * {@link Gallery} for commonly used subclasses of AdapterView. + * + *

+ *

Developer Guides

+ *

For more information about using AdapterView, read the + * Binding to Data with AdapterView + * developer guide.

+ */ +public abstract class IcsAdapterView extends ViewGroup { + + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the adapter does not want the item's view recycled. + */ + public static final int ITEM_VIEW_TYPE_IGNORE = -1; + + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the item is a header or footer. + */ + public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2; + + /** + * The position of the first child displayed + */ + @ViewDebug.ExportedProperty(category = "scrolling") + int mFirstPosition = 0; + + /** + * The offset in pixels from the top of the AdapterView to the top + * of the view to select during the next layout. + */ + int mSpecificTop; + + /** + * Position from which to start looking for mSyncRowId + */ + int mSyncPosition; + + /** + * Row id to look for when data has changed + */ + long mSyncRowId = INVALID_ROW_ID; + + /** + * Height of the view when mSyncPosition and mSyncRowId where set + */ + long mSyncHeight; + + /** + * True if we need to sync to mSyncRowId + */ + boolean mNeedSync = false; + + /** + * Indicates whether to sync based on the selection or position. Possible + * values are {@link #SYNC_SELECTED_POSITION} or + * {@link #SYNC_FIRST_POSITION}. + */ + int mSyncMode; + + /** + * Our height after the last layout + */ + private int mLayoutHeight; + + /** + * Sync based on the selected child + */ + static final int SYNC_SELECTED_POSITION = 0; + + /** + * Sync based on the first child displayed + */ + static final int SYNC_FIRST_POSITION = 1; + + /** + * Maximum amount of time to spend in {@link #findSyncPosition()} + */ + static final int SYNC_MAX_DURATION_MILLIS = 100; + + /** + * Indicates that this view is currently being laid out. + */ + boolean mInLayout = false; + + /** + * The listener that receives notifications when an item is selected. + */ + OnItemSelectedListener mOnItemSelectedListener; + + /** + * The listener that receives notifications when an item is clicked. + */ + OnItemClickListener mOnItemClickListener; + + /** + * The listener that receives notifications when an item is long clicked. + */ + OnItemLongClickListener mOnItemLongClickListener; + + /** + * True if the data has changed since the last layout + */ + boolean mDataChanged; + + /** + * The position within the adapter's data set of the item to select + * during the next layout. + */ + @ViewDebug.ExportedProperty(category = "list") + int mNextSelectedPosition = INVALID_POSITION; + + /** + * The item id of the item to select during the next layout. + */ + long mNextSelectedRowId = INVALID_ROW_ID; + + /** + * The position within the adapter's data set of the currently selected item. + */ + @ViewDebug.ExportedProperty(category = "list") + int mSelectedPosition = INVALID_POSITION; + + /** + * The item id of the currently selected item. + */ + long mSelectedRowId = INVALID_ROW_ID; + + /** + * View to show if there are no items to show. + */ + private View mEmptyView; + + /** + * The number of items in the current adapter. + */ + @ViewDebug.ExportedProperty(category = "list") + int mItemCount; + + /** + * The number of items in the adapter before a data changed event occurred. + */ + int mOldItemCount; + + /** + * Represents an invalid position. All valid positions are in the range 0 to 1 less than the + * number of items in the current adapter. + */ + public static final int INVALID_POSITION = -1; + + /** + * Represents an empty or invalid row id + */ + public static final long INVALID_ROW_ID = Long.MIN_VALUE; + + /** + * The last selected position we used when notifying + */ + int mOldSelectedPosition = INVALID_POSITION; + + /** + * The id of the last selected position we used when notifying + */ + long mOldSelectedRowId = INVALID_ROW_ID; + + /** + * Indicates what focusable state is requested when calling setFocusable(). + * In addition to this, this view has other criteria for actually + * determining the focusable state (such as whether its empty or the text + * filter is shown). + * + * @see #setFocusable(boolean) + * @see #checkFocus() + */ + private boolean mDesiredFocusableState; + private boolean mDesiredFocusableInTouchModeState; + + private SelectionNotifier mSelectionNotifier; + /** + * When set to true, calls to requestLayout() will not propagate up the parent hierarchy. + * This is used to layout the children during a layout pass. + */ + boolean mBlockLayoutRequests = false; + + public IcsAdapterView(Context context) { + super(context); + } + + public IcsAdapterView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public IcsAdapterView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been clicked. + * + * @param listener The callback that will be invoked. + */ + public void setOnItemClickListener(OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + /** + * @return The callback to be invoked with an item in this AdapterView has + * been clicked, or null id no callback has been set. + */ + public final OnItemClickListener getOnItemClickListener() { + return mOnItemClickListener; + } + + /** + * Call the OnItemClickListener, if it is defined. + * + * @param view The view within the AdapterView that was clicked. + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + * @return True if there was an assigned OnItemClickListener that was + * called, false otherwise is returned. + */ + public boolean performItemClick(View view, int position, long id) { + if (mOnItemClickListener != null) { + playSoundEffect(SoundEffectConstants.CLICK); + if (view != null) { + view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + } + mOnItemClickListener.onItemClick(/*this*/null, view, position, id); + return true; + } + + return false; + } + + /** + * Interface definition for a callback to be invoked when an item in this + * view has been clicked and held. + */ + public interface OnItemLongClickListener { + /** + * Callback method to be invoked when an item in this view has been + * clicked and held. + * + * Implementers can call getItemAtPosition(position) if they need to access + * the data associated with the selected item. + * + * @param parent The AbsListView where the click happened + * @param view The view within the AbsListView that was clicked + * @param position The position of the view in the list + * @param id The row id of the item that was clicked + * + * @return true if the callback consumed the long click, false otherwise + */ + boolean onItemLongClick(IcsAdapterView parent, View view, int position, long id); + } + + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been clicked and held + * + * @param listener The callback that will run + */ + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + if (!isLongClickable()) { + setLongClickable(true); + } + mOnItemLongClickListener = listener; + } + + /** + * @return The callback to be invoked with an item in this AdapterView has + * been clicked and held, or null id no callback as been set. + */ + public final OnItemLongClickListener getOnItemLongClickListener() { + return mOnItemLongClickListener; + } + + /** + * Interface definition for a callback to be invoked when + * an item in this view has been selected. + */ + public interface OnItemSelectedListener { + /** + *

Callback method to be invoked when an item in this view has been + * selected. This callback is invoked only when the newly selected + * position is different from the previously selected position or if + * there was no selected item.

+ * + * Impelmenters can call getItemAtPosition(position) if they need to access the + * data associated with the selected item. + * + * @param parent The AdapterView where the selection happened + * @param view The view within the AdapterView that was clicked + * @param position The position of the view in the adapter + * @param id The row id of the item that is selected + */ + void onItemSelected(IcsAdapterView parent, View view, int position, long id); + + /** + * Callback method to be invoked when the selection disappears from this + * view. The selection can disappear for instance when touch is activated + * or when the adapter becomes empty. + * + * @param parent The AdapterView that now contains no selected item. + */ + void onNothingSelected(IcsAdapterView parent); + } + + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been selected. + * + * @param listener The callback that will run + */ + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + mOnItemSelectedListener = listener; + } + + public final OnItemSelectedListener getOnItemSelectedListener() { + return mOnItemSelectedListener; + } + + /** + * Extra menu information provided to the + * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) } + * callback when a context menu is brought up for this AdapterView. + * + */ + public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo { + + public AdapterContextMenuInfo(View targetView, int position, long id) { + this.targetView = targetView; + this.position = position; + this.id = id; + } + + /** + * The child view for which the context menu is being displayed. This + * will be one of the children of this AdapterView. + */ + public View targetView; + + /** + * The position in the adapter for which the context menu is being + * displayed. + */ + public int position; + + /** + * The row id of the item for which the context menu is being displayed. + */ + public long id; + } + + /** + * Returns the adapter currently associated with this widget. + * + * @return The adapter used to provide this view's content. + */ + public abstract T getAdapter(); + + /** + * Sets the adapter that provides the data and the views to represent the data + * in this widget. + * + * @param adapter The adapter to use to create this view's content. + */ + public abstract void setAdapter(T adapter); + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child) { + throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * @param index Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child, int index) { + throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * @param params Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child, LayoutParams params) { + throw new UnsupportedOperationException("addView(View, LayoutParams) " + + "is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * @param index Ignored. + * @param params Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child, int index, LayoutParams params) { + throw new UnsupportedOperationException("addView(View, int, LayoutParams) " + + "is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void removeView(View child) { + throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param index Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void removeViewAt(int index) { + throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void removeAllViews() { + throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView"); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mLayoutHeight = getHeight(); + } + + /** + * Return the position of the currently selected item within the adapter's data set + * + * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected. + */ + @ViewDebug.CapturedViewProperty + public int getSelectedItemPosition() { + return mNextSelectedPosition; + } + + /** + * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID} + * if nothing is selected. + */ + @ViewDebug.CapturedViewProperty + public long getSelectedItemId() { + return mNextSelectedRowId; + } + + /** + * @return The view corresponding to the currently selected item, or null + * if nothing is selected + */ + public abstract View getSelectedView(); + + /** + * @return The data corresponding to the currently selected item, or + * null if there is nothing selected. + */ + public Object getSelectedItem() { + T adapter = getAdapter(); + int selection = getSelectedItemPosition(); + if (adapter != null && adapter.getCount() > 0 && selection >= 0) { + return adapter.getItem(selection); + } else { + return null; + } + } + + /** + * @return The number of items owned by the Adapter associated with this + * AdapterView. (This is the number of data items, which may be + * larger than the number of visible views.) + */ + @ViewDebug.CapturedViewProperty + public int getCount() { + return mItemCount; + } + + /** + * Get the position within the adapter's data set for the view, where view is a an adapter item + * or a descendant of an adapter item. + * + * @param view an adapter item, or a descendant of an adapter item. This must be visible in this + * AdapterView at the time of the call. + * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION} + * if the view does not correspond to a list item (or it is not currently visible). + */ + public int getPositionForView(View view) { + View listItem = view; + try { + View v; + while (!(v = (View) listItem.getParent()).equals(this)) { + listItem = v; + } + } catch (ClassCastException e) { + // We made it up to the window without find this list view + return INVALID_POSITION; + } + + // Search the children for the list item + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i).equals(listItem)) { + return mFirstPosition + i; + } + } + + // Child not found! + return INVALID_POSITION; + } + + /** + * Returns the position within the adapter's data set for the first item + * displayed on screen. + * + * @return The position within the adapter's data set + */ + public int getFirstVisiblePosition() { + return mFirstPosition; + } + + /** + * Returns the position within the adapter's data set for the last item + * displayed on screen. + * + * @return The position within the adapter's data set + */ + public int getLastVisiblePosition() { + return mFirstPosition + getChildCount() - 1; + } + + /** + * Sets the currently selected item. To support accessibility subclasses that + * override this method must invoke the overriden super method first. + * + * @param position Index (starting at 0) of the data item to be selected. + */ + public abstract void setSelection(int position); + + /** + * Sets the view to show if the adapter is empty + */ + public void setEmptyView(View emptyView) { + mEmptyView = emptyView; + + final T adapter = getAdapter(); + final boolean empty = ((adapter == null) || adapter.isEmpty()); + updateEmptyStatus(empty); + } + + /** + * When the current adapter is empty, the AdapterView can display a special view + * call the empty view. The empty view is used to provide feedback to the user + * that no data is available in this AdapterView. + * + * @return The view to show if the adapter is empty. + */ + public View getEmptyView() { + return mEmptyView; + } + + /** + * Indicates whether this view is in filter mode. Filter mode can for instance + * be enabled by a user when typing on the keyboard. + * + * @return True if the view is in filter mode, false otherwise. + */ + boolean isInFilterMode() { + return false; + } + + @Override + public void setFocusable(boolean focusable) { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + + mDesiredFocusableState = focusable; + if (!focusable) { + mDesiredFocusableInTouchModeState = false; + } + + super.setFocusable(focusable && (!empty || isInFilterMode())); + } + + @Override + public void setFocusableInTouchMode(boolean focusable) { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + + mDesiredFocusableInTouchModeState = focusable; + if (focusable) { + mDesiredFocusableState = true; + } + + super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode())); + } + + void checkFocus() { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + final boolean focusable = !empty || isInFilterMode(); + // The order in which we set focusable in touch mode/focusable may matter + // for the client, see View.setFocusableInTouchMode() comments for more + // details + super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); + super.setFocusable(focusable && mDesiredFocusableState); + if (mEmptyView != null) { + updateEmptyStatus((adapter == null) || adapter.isEmpty()); + } + } + + /** + * Update the status of the list based on the empty parameter. If empty is true and + * we have an empty view, display it. In all the other cases, make sure that the listview + * is VISIBLE and that the empty view is GONE (if it's not null). + */ + private void updateEmptyStatus(boolean empty) { + if (isInFilterMode()) { + empty = false; + } + + if (empty) { + if (mEmptyView != null) { + mEmptyView.setVisibility(View.VISIBLE); + setVisibility(View.GONE); + } else { + // If the caller just removed our empty view, make sure the list view is visible + setVisibility(View.VISIBLE); + } + + // We are now GONE, so pending layouts will not be dispatched. + // Force one here to make sure that the state of the list matches + // the state of the adapter. + if (mDataChanged) { + this.onLayout(false, getLeft(), getTop(), getRight(), getBottom()); + } + } else { + if (mEmptyView != null) mEmptyView.setVisibility(View.GONE); + setVisibility(View.VISIBLE); + } + } + + /** + * Gets the data associated with the specified position in the list. + * + * @param position Which data to get + * @return The data associated with the specified position in the list + */ + public Object getItemAtPosition(int position) { + T adapter = getAdapter(); + return (adapter == null || position < 0) ? null : adapter.getItem(position); + } + + public long getItemIdAtPosition(int position) { + T adapter = getAdapter(); + return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position); + } + + @Override + public void setOnClickListener(OnClickListener l) { + throw new RuntimeException("Don't call setOnClickListener for an AdapterView. " + + "You probably want setOnItemClickListener instead"); + } + + /** + * Override to prevent freezing of any views created by the adapter. + */ + @Override + protected void dispatchSaveInstanceState(SparseArray container) { + dispatchFreezeSelfOnly(container); + } + + /** + * Override to prevent thawing of any views created by the adapter. + */ + @Override + protected void dispatchRestoreInstanceState(SparseArray container) { + dispatchThawSelfOnly(container); + } + + class AdapterDataSetObserver extends DataSetObserver { + + private Parcelable mInstanceState = null; + + @Override + public void onChanged() { + mDataChanged = true; + mOldItemCount = mItemCount; + mItemCount = getAdapter().getCount(); + + // Detect the case where a cursor that was previously invalidated has + // been repopulated with new data. + if (IcsAdapterView.this.getAdapter().hasStableIds() && mInstanceState != null + && mOldItemCount == 0 && mItemCount > 0) { + IcsAdapterView.this.onRestoreInstanceState(mInstanceState); + mInstanceState = null; + } else { + rememberSyncState(); + } + checkFocus(); + requestLayout(); + } + + @Override + public void onInvalidated() { + mDataChanged = true; + + if (IcsAdapterView.this.getAdapter().hasStableIds()) { + // Remember the current state for the case where our hosting activity is being + // stopped and later restarted + mInstanceState = IcsAdapterView.this.onSaveInstanceState(); + } + + // Data is invalid so we should reset our state + mOldItemCount = mItemCount; + mItemCount = 0; + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; + mNextSelectedPosition = INVALID_POSITION; + mNextSelectedRowId = INVALID_ROW_ID; + mNeedSync = false; + + checkFocus(); + requestLayout(); + } + + public void clearSavedState() { + mInstanceState = null; + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(mSelectionNotifier); + } + + private class SelectionNotifier implements Runnable { + public void run() { + if (mDataChanged) { + // Data has changed between when this SelectionNotifier + // was posted and now. We need to wait until the AdapterView + // has been synched to the new data. + if (getAdapter() != null) { + post(this); + } + } else { + fireOnSelected(); + } + } + } + + void selectionChanged() { + if (mOnItemSelectedListener != null) { + if (mInLayout || mBlockLayoutRequests) { + // If we are in a layout traversal, defer notification + // by posting. This ensures that the view tree is + // in a consistent state and is able to accomodate + // new layout or invalidate requests. + if (mSelectionNotifier == null) { + mSelectionNotifier = new SelectionNotifier(); + } + post(mSelectionNotifier); + } else { + fireOnSelected(); + } + } + + // we fire selection events here not in View + if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + } + + private void fireOnSelected() { + if (mOnItemSelectedListener == null) + return; + + int selection = this.getSelectedItemPosition(); + if (selection >= 0) { + View v = getSelectedView(); + mOnItemSelectedListener.onItemSelected(this, v, selection, + getAdapter().getItemId(selection)); + } else { + mOnItemSelectedListener.onNothingSelected(this); + } + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + View selectedView = getSelectedView(); + if (selectedView != null && selectedView.getVisibility() == VISIBLE + && selectedView.dispatchPopulateAccessibilityEvent(event)) { + return true; + } + return false; + } + + @Override + public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (super.onRequestSendAccessibilityEvent(child, event)) { + // Add a record for ourselves as well. + AccessibilityEvent record = AccessibilityEvent.obtain(); + onInitializeAccessibilityEvent(record); + // Populate with the text of the requesting child. + child.dispatchPopulateAccessibilityEvent(record); + event.appendRecord(record); + return true; + } + return false; + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(isScrollableForAccessibility()); + View selectedView = getSelectedView(); + if (selectedView != null) { + info.setEnabled(selectedView.isEnabled()); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(isScrollableForAccessibility()); + View selectedView = getSelectedView(); + if (selectedView != null) { + event.setEnabled(selectedView.isEnabled()); + } + event.setCurrentItemIndex(getSelectedItemPosition()); + event.setFromIndex(getFirstVisiblePosition()); + event.setToIndex(getLastVisiblePosition()); + event.setItemCount(getCount()); + } + + private boolean isScrollableForAccessibility() { + T adapter = getAdapter(); + if (adapter != null) { + final int itemCount = adapter.getCount(); + return itemCount > 0 + && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1); + } + return false; + } + + @Override + protected boolean canAnimate() { + return super.canAnimate() && mItemCount > 0; + } + + void handleDataChanged() { + final int count = mItemCount; + boolean found = false; + + if (count > 0) { + + int newPos; + + // Find the row we are supposed to sync to + if (mNeedSync) { + // Update this first, since setNextSelectedPositionInt inspects + // it + mNeedSync = false; + + // See if we can find a position in the new data with the same + // id as the old selection + newPos = findSyncPosition(); + if (newPos >= 0) { + // Verify that new selection is selectable + int selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos == newPos) { + // Same row id is selected + setNextSelectedPositionInt(newPos); + found = true; + } + } + } + if (!found) { + // Try to use the same position if we can't find matching data + newPos = getSelectedItemPosition(); + + // Pin position to the available range + if (newPos >= count) { + newPos = count - 1; + } + if (newPos < 0) { + newPos = 0; + } + + // Make sure we select something selectable -- first look down + int selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos < 0) { + // Looking down didn't work -- try looking up + selectablePos = lookForSelectablePosition(newPos, false); + } + if (selectablePos >= 0) { + setNextSelectedPositionInt(selectablePos); + checkSelectionChanged(); + found = true; + } + } + } + if (!found) { + // Nothing is selected + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; + mNextSelectedPosition = INVALID_POSITION; + mNextSelectedRowId = INVALID_ROW_ID; + mNeedSync = false; + checkSelectionChanged(); + } + } + + void checkSelectionChanged() { + if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { + selectionChanged(); + mOldSelectedPosition = mSelectedPosition; + mOldSelectedRowId = mSelectedRowId; + } + } + + /** + * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition + * and then alternates between moving up and moving down until 1) we find the right position, or + * 2) we run out of time, or 3) we have looked at every position + * + * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't + * be found + */ + int findSyncPosition() { + int count = mItemCount; + + if (count == 0) { + return INVALID_POSITION; + } + + long idToMatch = mSyncRowId; + int seed = mSyncPosition; + + // If there isn't a selection don't hunt for it + if (idToMatch == INVALID_ROW_ID) { + return INVALID_POSITION; + } + + // Pin seed to reasonable values + seed = Math.max(0, seed); + seed = Math.min(count - 1, seed); + + long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; + + long rowId; + + // first position scanned so far + int first = seed; + + // last position scanned so far + int last = seed; + + // True if we should move down on the next iteration + boolean next = false; + + // True when we have looked at the first item in the data + boolean hitFirst; + + // True when we have looked at the last item in the data + boolean hitLast; + + // Get the item ID locally (instead of getItemIdAtPosition), so + // we need the adapter + T adapter = getAdapter(); + if (adapter == null) { + return INVALID_POSITION; + } + + while (SystemClock.uptimeMillis() <= endTime) { + rowId = adapter.getItemId(seed); + if (rowId == idToMatch) { + // Found it! + return seed; + } + + hitLast = last == count - 1; + hitFirst = first == 0; + + if (hitLast && hitFirst) { + // Looked at everything + break; + } + + if (hitFirst || (next && !hitLast)) { + // Either we hit the top, or we are trying to move down + last++; + seed = last; + // Try going up next time + next = false; + } else if (hitLast || (!next && !hitFirst)) { + // Either we hit the bottom, or we are trying to move up + first--; + seed = first; + // Try going down next time + next = true; + } + + } + + return INVALID_POSITION; + } + + /** + * Find a position that can be selected (i.e., is not a separator). + * + * @param position The starting position to look at. + * @param lookDown Whether to look down for other positions. + * @return The next selectable position starting at position and then searching either up or + * down. Returns {@link #INVALID_POSITION} if nothing can be found. + */ + int lookForSelectablePosition(int position, boolean lookDown) { + return position; + } + + /** + * Utility to keep mSelectedPosition and mSelectedRowId in sync + * @param position Our current position + */ + void setSelectedPositionInt(int position) { + mSelectedPosition = position; + mSelectedRowId = getItemIdAtPosition(position); + } + + /** + * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync + * @param position Intended value for mSelectedPosition the next time we go + * through layout + */ + void setNextSelectedPositionInt(int position) { + mNextSelectedPosition = position; + mNextSelectedRowId = getItemIdAtPosition(position); + // If we are trying to sync to the selection, update that too + if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { + mSyncPosition = position; + mSyncRowId = mNextSelectedRowId; + } + } + + /** + * Remember enough information to restore the screen state when the data has + * changed. + * + */ + void rememberSyncState() { + if (getChildCount() > 0) { + mNeedSync = true; + mSyncHeight = mLayoutHeight; + if (mSelectedPosition >= 0) { + // Sync the selection state + View v = getChildAt(mSelectedPosition - mFirstPosition); + mSyncRowId = mNextSelectedRowId; + mSyncPosition = mNextSelectedPosition; + if (v != null) { + mSpecificTop = v.getTop(); + } + mSyncMode = SYNC_SELECTED_POSITION; + } else { + // Sync the based on the offset of the first view + View v = getChildAt(0); + T adapter = getAdapter(); + if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { + mSyncRowId = adapter.getItemId(mFirstPosition); + } else { + mSyncRowId = NO_ID; + } + mSyncPosition = mFirstPosition; + if (v != null) { + mSpecificTop = v.getTop(); + } + mSyncMode = SYNC_FIRST_POSITION; + } + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java new file mode 100644 index 000000000..2edda7264 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java @@ -0,0 +1,160 @@ +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import com.actionbarsherlock.internal.nineoldandroids.widget.NineLinearLayout; + +/** + * A simple extension of a regular linear layout that supports the divider API + * of Android 4.0+. + */ +public class IcsLinearLayout extends NineLinearLayout { + private static final int[] LinearLayout = new int[] { + /* 0 */ android.R.attr.divider, + /* 1 */ android.R.attr.showDividers, + /* 2 */ android.R.attr.dividerPadding, + }; + private static final int LinearLayout_divider = 0; + private static final int LinearLayout_showDividers = 1; + private static final int LinearLayout_dividerPadding = 2; + + /** + * Don't show any dividers. + */ + public static final int SHOW_DIVIDER_NONE = 0; + /** + * Show a divider at the beginning of the group. + */ + public static final int SHOW_DIVIDER_BEGINNING = 1; + /** + * Show dividers between each item in the group. + */ + public static final int SHOW_DIVIDER_MIDDLE = 2; + /** + * Show a divider at the end of the group. + */ + public static final int SHOW_DIVIDER_END = 4; + + + private Drawable mDivider; + private int mDividerWidth; + private int mShowDividers; + private int mDividerPadding; + + + public IcsLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/LinearLayout); + + setDividerDrawable(a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider)); + mShowDividers = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_showDividers, SHOW_DIVIDER_NONE); + mDividerPadding = a.getDimensionPixelSize(/*com.android.internal.R.styleable.*/LinearLayout_dividerPadding, 0); + + a.recycle(); + } + + /** + * Set a drawable to be used as a divider between items. + * @param divider Drawable that will divide each item. + * @see #setShowDividers(int) + */ + public void setDividerDrawable(Drawable divider) { + if (divider == mDivider) { + return; + } + mDivider = divider; + if (divider != null) { + mDividerWidth = divider.getIntrinsicWidth(); + } else { + mDividerWidth = 0; + } + setWillNotDraw(divider == null); + requestLayout(); + } + + /** + * Get the width of the current divider drawable. + * + * @hide Used internally by framework. + */ + public int getDividerWidth() { + return mDividerWidth; + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { + final int index = indexOfChild(child); + if (hasDividerBeforeChildAt(index)) { + //Account for the divider by pushing everything left + ((LayoutParams)child.getLayoutParams()).leftMargin = mDividerWidth; + } + super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDivider != null) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child != null && child.getVisibility() != GONE) { + if (hasDividerBeforeChildAt(i)) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int left = child.getLeft() - lp.leftMargin; + drawVerticalDivider(canvas, left); + } + } + } + + if (hasDividerBeforeChildAt(count)) { + final View child = getChildAt(count - 1); + int right = 0; + if (child == null) { + right = getWidth() - getPaddingRight() - mDividerWidth; + } else { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + right = child.getRight() + lp.rightMargin; + } + drawVerticalDivider(canvas, right); + } + } + + super.onDraw(canvas); + } + + void drawVerticalDivider(Canvas canvas, int left) { + mDivider.setBounds(left, getPaddingTop() + mDividerPadding, + left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding); + mDivider.draw(canvas); + } + + /** + * Determines where to position dividers between children. + * + * @param childIndex Index of child to check for preceding divider + * @return true if there should be a divider before the child at childIndex + * @hide Pending API consideration. Currently only used internally by the system. + */ + protected boolean hasDividerBeforeChildAt(int childIndex) { + if (childIndex == 0) { + return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0; + } else if (childIndex == getChildCount()) { + return (mShowDividers & SHOW_DIVIDER_END) != 0; + } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) { + boolean hasVisibleViewBefore = false; + for (int i = childIndex - 1; i >= 0; i--) { + if (getChildAt(i).getVisibility() != GONE) { + hasVisibleViewBefore = true; + break; + } + } + return hasVisibleViewBefore; + } + return false; + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsListPopupWindow.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsListPopupWindow.java new file mode 100644 index 000000000..e06bcb1c4 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsListPopupWindow.java @@ -0,0 +1,640 @@ +package com.actionbarsherlock.internal.widget; + +import com.actionbarsherlock.R; + +import android.content.Context; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.PopupWindow; + +/** + * A proxy between pre- and post-Honeycomb implementations of this class. + */ +public class IcsListPopupWindow { + /** + * This value controls the length of time that the user + * must leave a pointer down without scrolling to expand + * the autocomplete dropdown list to cover the IME. + */ + private static final int EXPAND_LIST_TIMEOUT = 250; + + private Context mContext; + private PopupWindow mPopup; + private ListAdapter mAdapter; + private DropDownListView mDropDownList; + + private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT; + private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT; + private int mDropDownHorizontalOffset; + private int mDropDownVerticalOffset; + private boolean mDropDownVerticalOffsetSet; + + private int mListItemExpandMaximum = Integer.MAX_VALUE; + + private View mPromptView; + private int mPromptPosition = POSITION_PROMPT_ABOVE; + + private DataSetObserver mObserver; + + private View mDropDownAnchorView; + + private Drawable mDropDownListHighlight; + + private AdapterView.OnItemClickListener mItemClickListener; + private AdapterView.OnItemSelectedListener mItemSelectedListener; + + private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable(); + private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor(); + private final PopupScrollListener mScrollListener = new PopupScrollListener(); + private final ListSelectorHider mHideSelector = new ListSelectorHider(); + + private Handler mHandler = new Handler(); + + private Rect mTempRect = new Rect(); + + private boolean mModal; + + public static final int POSITION_PROMPT_ABOVE = 0; + public static final int POSITION_PROMPT_BELOW = 1; + + public IcsListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { + mContext = context; + mPopup = new PopupWindow(context, attrs, defStyleAttr); + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + } + + public IcsListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + mContext = context; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + Context wrapped = new ContextThemeWrapper(context, defStyleRes); + mPopup = new PopupWindow(wrapped, attrs, defStyleAttr); + } else { + mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes); + } + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + } + + public void setAdapter(ListAdapter adapter) { + if (mObserver == null) { + mObserver = new PopupDataSetObserver(); + } else if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mObserver); + } + mAdapter = adapter; + if (mAdapter != null) { + adapter.registerDataSetObserver(mObserver); + } + + if (mDropDownList != null) { + mDropDownList.setAdapter(mAdapter); + } + } + + public void setPromptPosition(int position) { + mPromptPosition = position; + } + + public void setModal(boolean modal) { + mModal = true; + mPopup.setFocusable(modal); + } + + public void setBackgroundDrawable(Drawable d) { + mPopup.setBackgroundDrawable(d); + } + + public void setAnchorView(View anchor) { + mDropDownAnchorView = anchor; + } + + public void setHorizontalOffset(int offset) { + mDropDownHorizontalOffset = offset; + } + + public void setVerticalOffset(int offset) { + mDropDownVerticalOffset = offset; + mDropDownVerticalOffsetSet = true; + } + + public void setContentWidth(int width) { + Drawable popupBackground = mPopup.getBackground(); + if (popupBackground != null) { + popupBackground.getPadding(mTempRect); + mDropDownWidth = mTempRect.left + mTempRect.right + width; + } else { + mDropDownWidth = width; + } + } + + public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) { + mItemClickListener = clickListener; + } + + public void show() { + int height = buildDropDown(); + + int widthSpec = 0; + int heightSpec = 0; + + boolean noInputMethod = isInputMethodNotNeeded(); + //XXX mPopup.setAllowScrollingAnchorParent(!noInputMethod); + + if (mPopup.isShowing()) { + if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { + // The call to PopupWindow's update method below can accept -1 for any + // value you do not want to update. + widthSpec = -1; + } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { + widthSpec = mDropDownAnchorView.getWidth(); + } else { + widthSpec = mDropDownWidth; + } + + if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { + // The call to PopupWindow's update method below can accept -1 for any + // value you do not want to update. + heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; + if (noInputMethod) { + mPopup.setWindowLayoutMode( + mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? + ViewGroup.LayoutParams.MATCH_PARENT : 0, 0); + } else { + mPopup.setWindowLayoutMode( + mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? + ViewGroup.LayoutParams.MATCH_PARENT : 0, + ViewGroup.LayoutParams.MATCH_PARENT); + } + } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { + heightSpec = height; + } else { + heightSpec = mDropDownHeight; + } + + mPopup.setOutsideTouchable(true); + + mPopup.update(mDropDownAnchorView, mDropDownHorizontalOffset, + mDropDownVerticalOffset, widthSpec, heightSpec); + } else { + if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { + widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; + } else { + if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { + mPopup.setWidth(mDropDownAnchorView.getWidth()); + } else { + mPopup.setWidth(mDropDownWidth); + } + } + + if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { + heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; + } else { + if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { + mPopup.setHeight(height); + } else { + mPopup.setHeight(mDropDownHeight); + } + } + + mPopup.setWindowLayoutMode(widthSpec, heightSpec); + //XXX mPopup.setClipToScreenEnabled(true); + + // use outside touchable to dismiss drop down when touching outside of it, so + // only set this if the dropdown is not always visible + mPopup.setOutsideTouchable(true); + mPopup.setTouchInterceptor(mTouchInterceptor); + mPopup.showAsDropDown(mDropDownAnchorView, + mDropDownHorizontalOffset, mDropDownVerticalOffset); + mDropDownList.setSelection(ListView.INVALID_POSITION); + + if (!mModal || mDropDownList.isInTouchMode()) { + clearListSelection(); + } + if (!mModal) { + mHandler.post(mHideSelector); + } + } + } + + public void dismiss() { + mPopup.dismiss(); + if (mPromptView != null) { + final ViewParent parent = mPromptView.getParent(); + if (parent instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) parent; + group.removeView(mPromptView); + } + } + mPopup.setContentView(null); + mDropDownList = null; + mHandler.removeCallbacks(mResizePopupRunnable); + } + + public void setOnDismissListener(PopupWindow.OnDismissListener listener) { + mPopup.setOnDismissListener(listener); + } + + public void setInputMethodMode(int mode) { + mPopup.setInputMethodMode(mode); + } + + public void clearListSelection() { + final DropDownListView list = mDropDownList; + if (list != null) { + // WARNING: Please read the comment where mListSelectionHidden is declared + list.mListSelectionHidden = true; + //XXX list.hideSelector(); + list.requestLayout(); + } + } + + public boolean isShowing() { + return mPopup.isShowing(); + } + + private boolean isInputMethodNotNeeded() { + return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; + } + + public ListView getListView() { + return mDropDownList; + } + + private int buildDropDown() { + ViewGroup dropDownView; + int otherHeights = 0; + + if (mDropDownList == null) { + Context context = mContext; + + mDropDownList = new DropDownListView(context, !mModal); + if (mDropDownListHighlight != null) { + mDropDownList.setSelector(mDropDownListHighlight); + } + mDropDownList.setAdapter(mAdapter); + mDropDownList.setOnItemClickListener(mItemClickListener); + mDropDownList.setFocusable(true); + mDropDownList.setFocusableInTouchMode(true); + mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView parent, View view, + int position, long id) { + + if (position != -1) { + DropDownListView dropDownList = mDropDownList; + + if (dropDownList != null) { + dropDownList.mListSelectionHidden = false; + } + } + } + + public void onNothingSelected(AdapterView parent) { + } + }); + mDropDownList.setOnScrollListener(mScrollListener); + + if (mItemSelectedListener != null) { + mDropDownList.setOnItemSelectedListener(mItemSelectedListener); + } + + dropDownView = mDropDownList; + + View hintView = mPromptView; + if (hintView != null) { + // if an hint has been specified, we accomodate more space for it and + // add a text view in the drop down menu, at the bottom of the list + LinearLayout hintContainer = new LinearLayout(context); + hintContainer.setOrientation(LinearLayout.VERTICAL); + + LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f + ); + + switch (mPromptPosition) { + case POSITION_PROMPT_BELOW: + hintContainer.addView(dropDownView, hintParams); + hintContainer.addView(hintView); + break; + + case POSITION_PROMPT_ABOVE: + hintContainer.addView(hintView); + hintContainer.addView(dropDownView, hintParams); + break; + + default: + break; + } + + // measure the hint's height to find how much more vertical space + // we need to add to the drop down's height + int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST); + int heightSpec = MeasureSpec.UNSPECIFIED; + hintView.measure(widthSpec, heightSpec); + + hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams(); + otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin + + hintParams.bottomMargin; + + dropDownView = hintContainer; + } + + mPopup.setContentView(dropDownView); + } else { + dropDownView = (ViewGroup) mPopup.getContentView(); + final View view = mPromptView; + if (view != null) { + LinearLayout.LayoutParams hintParams = + (LinearLayout.LayoutParams) view.getLayoutParams(); + otherHeights = view.getMeasuredHeight() + hintParams.topMargin + + hintParams.bottomMargin; + } + } + + // getMaxAvailableHeight() subtracts the padding, so we put it back + // to get the available height for the whole window + int padding = 0; + Drawable background = mPopup.getBackground(); + if (background != null) { + background.getPadding(mTempRect); + padding = mTempRect.top + mTempRect.bottom; + + // If we don't have an explicit vertical offset, determine one from the window + // background so that content will line up. + if (!mDropDownVerticalOffsetSet) { + mDropDownVerticalOffset = -mTempRect.top; + } + } + + // Max height available on the screen for a popup. + boolean ignoreBottomDecorations = + mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; + final int maxHeight = /*mPopup.*/getMaxAvailableHeight( + mDropDownAnchorView, mDropDownVerticalOffset, ignoreBottomDecorations); + + if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { + return maxHeight + padding; + } + + final int listContent = /*mDropDownList.*/measureHeightOfChildren(MeasureSpec.UNSPECIFIED, + 0, -1/*ListView.NO_POSITION*/, maxHeight - otherHeights, -1); + // add padding only if the list has items in it, that way we don't show + // the popup if it is not needed + if (listContent > 0) otherHeights += padding; + + return listContent + otherHeights; + } + + private int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { + final Rect displayFrame = new Rect(); + anchor.getWindowVisibleDisplayFrame(displayFrame); + + final int[] anchorPos = new int[2]; + anchor.getLocationOnScreen(anchorPos); + + int bottomEdge = displayFrame.bottom; + if (ignoreBottomDecorations) { + Resources res = anchor.getContext().getResources(); + bottomEdge = res.getDisplayMetrics().heightPixels; + } + final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; + final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; + + // anchorPos[1] is distance from anchor to top of screen + int returnedHeight = Math.max(distanceToBottom, distanceToTop); + if (mPopup.getBackground() != null) { + mPopup.getBackground().getPadding(mTempRect); + returnedHeight -= mTempRect.top + mTempRect.bottom; + } + + return returnedHeight; + } + + private int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, + final int maxHeight, int disallowPartialChildPosition) { + + final ListAdapter adapter = mAdapter; + if (adapter == null) { + return mDropDownList.getListPaddingTop() + mDropDownList.getListPaddingBottom(); + } + + // Include the padding of the list + int returnedHeight = mDropDownList.getListPaddingTop() + mDropDownList.getListPaddingBottom(); + final int dividerHeight = ((mDropDownList.getDividerHeight() > 0) && mDropDownList.getDivider() != null) ? mDropDownList.getDividerHeight() : 0; + // The previous height value that was less than maxHeight and contained + // no partial children + int prevHeightWithoutPartialChild = 0; + int i; + View child; + + // mItemCount - 1 since endPosition parameter is inclusive + endPosition = (endPosition == -1/*NO_POSITION*/) ? adapter.getCount() - 1 : endPosition; + + for (i = startPosition; i <= endPosition; ++i) { + child = mAdapter.getView(i, null, mDropDownList); + if (mDropDownList.getCacheColorHint() != 0) { + child.setDrawingCacheBackgroundColor(mDropDownList.getCacheColorHint()); + } + + measureScrapChild(child, i, widthMeasureSpec); + + if (i > 0) { + // Count the divider for all but one child + returnedHeight += dividerHeight; + } + + returnedHeight += child.getMeasuredHeight(); + + if (returnedHeight >= maxHeight) { + // We went over, figure out which height to return. If returnedHeight > maxHeight, + // then the i'th position did not fit completely. + return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) + && (i > disallowPartialChildPosition) // We've past the min pos + && (prevHeightWithoutPartialChild > 0) // We have a prev height + && (returnedHeight != maxHeight) // i'th child did not fit completely + ? prevHeightWithoutPartialChild + : maxHeight; + } + + if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { + prevHeightWithoutPartialChild = returnedHeight; + } + } + + // At this point, we went through the range of children, and they each + // completely fit, so return the returnedHeight + return returnedHeight; + } + private void measureScrapChild(View child, int position, int widthMeasureSpec) { + ListView.LayoutParams p = (ListView.LayoutParams) child.getLayoutParams(); + if (p == null) { + p = new ListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, 0); + child.setLayoutParams(p); + } + //XXX p.viewType = mAdapter.getItemViewType(position); + //XXX p.forceAdd = true; + + int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, + mDropDownList.getPaddingLeft() + mDropDownList.getPaddingRight(), p.width); + int lpHeight = p.height; + int childHeightSpec; + if (lpHeight > 0) { + childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); + } else { + childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + child.measure(childWidthSpec, childHeightSpec); + } + + private static class DropDownListView extends ListView { + /* + * WARNING: This is a workaround for a touch mode issue. + * + * Touch mode is propagated lazily to windows. This causes problems in + * the following scenario: + * - Type something in the AutoCompleteTextView and get some results + * - Move down with the d-pad to select an item in the list + * - Move up with the d-pad until the selection disappears + * - Type more text in the AutoCompleteTextView *using the soft keyboard* + * and get new results; you are now in touch mode + * - The selection comes back on the first item in the list, even though + * the list is supposed to be in touch mode + * + * Using the soft keyboard triggers the touch mode change but that change + * is propagated to our window only after the first list layout, therefore + * after the list attempts to resurrect the selection. + * + * The trick to work around this issue is to pretend the list is in touch + * mode when we know that the selection should not appear, that is when + * we know the user moved the selection away from the list. + * + * This boolean is set to true whenever we explicitly hide the list's + * selection and reset to false whenever we know the user moved the + * selection back to the list. + * + * When this boolean is true, isInTouchMode() returns true, otherwise it + * returns super.isInTouchMode(). + */ + private boolean mListSelectionHidden; + + private boolean mHijackFocus; + + public DropDownListView(Context context, boolean hijackFocus) { + super(context, null, /*com.android.internal.*/R.attr.dropDownListViewStyle); + mHijackFocus = hijackFocus; + // TODO: Add an API to control this + setCacheColorHint(0); // Transparent, since the background drawable could be anything. + } + + //XXX @Override + //View obtainView(int position, boolean[] isScrap) { + // View view = super.obtainView(position, isScrap); + + // if (view instanceof TextView) { + // ((TextView) view).setHorizontallyScrolling(true); + // } + + // return view; + //} + + @Override + public boolean isInTouchMode() { + // WARNING: Please read the comment where mListSelectionHidden is declared + return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); + } + + @Override + public boolean hasWindowFocus() { + return mHijackFocus || super.hasWindowFocus(); + } + + @Override + public boolean isFocused() { + return mHijackFocus || super.isFocused(); + } + + @Override + public boolean hasFocus() { + return mHijackFocus || super.hasFocus(); + } + } + + private class PopupDataSetObserver extends DataSetObserver { + @Override + public void onChanged() { + if (isShowing()) { + // Resize the popup to fit new content + show(); + } + } + + @Override + public void onInvalidated() { + dismiss(); + } + } + + private class ListSelectorHider implements Runnable { + public void run() { + clearListSelection(); + } + } + + private class ResizePopupRunnable implements Runnable { + public void run() { + if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() && + mDropDownList.getChildCount() <= mListItemExpandMaximum) { + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + show(); + } + } + } + + private class PopupTouchInterceptor implements OnTouchListener { + public boolean onTouch(View v, MotionEvent event) { + final int action = event.getAction(); + final int x = (int) event.getX(); + final int y = (int) event.getY(); + + if (action == MotionEvent.ACTION_DOWN && + mPopup != null && mPopup.isShowing() && + (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) { + mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT); + } else if (action == MotionEvent.ACTION_UP) { + mHandler.removeCallbacks(mResizePopupRunnable); + } + return false; + } + } + + private class PopupScrollListener implements ListView.OnScrollListener { + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + + } + + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (scrollState == SCROLL_STATE_TOUCH_SCROLL && + !isInputMethodNotNeeded() && mPopup.getContentView() != null) { + mHandler.removeCallbacks(mResizePopupRunnable); + mResizePopupRunnable.run(); + } + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java new file mode 100644 index 000000000..1c02d4aca --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java @@ -0,0 +1,1193 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.graphics.drawable.shapes.Shape; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewDebug; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.Transformation; +import android.widget.RemoteViews.RemoteView; + + +/** + *

+ * Visual indicator of progress in some operation. Displays a bar to the user + * representing how far the operation has progressed; the application can + * change the amount of progress (modifying the length of the bar) as it moves + * forward. There is also a secondary progress displayable on a progress bar + * which is useful for displaying intermediate progress, such as the buffer + * level during a streaming playback progress bar. + *

+ * + *

+ * A progress bar can also be made indeterminate. In indeterminate mode, the + * progress bar shows a cyclic animation without an indication of progress. This mode is used by + * applications when the length of the task is unknown. The indeterminate progress bar can be either + * a spinning wheel or a horizontal bar. + *

+ * + *

The following code example shows how a progress bar can be used from + * a worker thread to update the user interface to notify the user of progress: + *

+ * + *
+ * public class MyActivity extends Activity {
+ *     private static final int PROGRESS = 0x1;
+ *
+ *     private ProgressBar mProgress;
+ *     private int mProgressStatus = 0;
+ *
+ *     private Handler mHandler = new Handler();
+ *
+ *     protected void onCreate(Bundle icicle) {
+ *         super.onCreate(icicle);
+ *
+ *         setContentView(R.layout.progressbar_activity);
+ *
+ *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
+ *
+ *         // Start lengthy operation in a background thread
+ *         new Thread(new Runnable() {
+ *             public void run() {
+ *                 while (mProgressStatus < 100) {
+ *                     mProgressStatus = doWork();
+ *
+ *                     // Update the progress bar
+ *                     mHandler.post(new Runnable() {
+ *                         public void run() {
+ *                             mProgress.setProgress(mProgressStatus);
+ *                         }
+ *                     });
+ *                 }
+ *             }
+ *         }).start();
+ *     }
+ * }
+ * + *

To add a progress bar to a layout file, you can use the {@code <ProgressBar>} element. + * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a + * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal + * Widget.ProgressBar.Horizontal} style, like so:

+ * + *
+ * <ProgressBar
+ *     style="@android:style/Widget.ProgressBar.Horizontal"
+ *     ... />
+ * + *

If you will use the progress bar to show real progress, you must use the horizontal bar. You + * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or + * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If + * necessary, you can adjust the maximum value (the value for a full bar) using the {@link + * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed + * below.

+ * + *

Another common style to apply to the progress bar is {@link + * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller + * version of the spinning wheel—useful when waiting for content to load. + * For example, you can insert this kind of progress bar into your default layout for + * a view that will be populated by some content fetched from the Internet—the spinning wheel + * appears immediately and when your application receives the content, it replaces the progress bar + * with the loaded content. For example:

+ * + *
+ * <LinearLayout
+ *     android:orientation="horizontal"
+ *     ... >
+ *     <ProgressBar
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         style="@android:style/Widget.ProgressBar.Small"
+ *         android:layout_marginRight="5dp" />
+ *     <TextView
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:text="@string/loading" />
+ * </LinearLayout>
+ * + *

Other progress bar styles provided by the system include:

+ *
    + *
  • {@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}
  • + *
  • {@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}
  • + *
  • {@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}
  • + *
  • {@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}
  • + *
  • {@link android.R.style#Widget_ProgressBar_Small_Inverse + * Widget.ProgressBar.Small.Inverse}
  • + *
  • {@link android.R.style#Widget_ProgressBar_Large_Inverse + * Widget.ProgressBar.Large.Inverse}
  • + *
+ *

The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary + * if your application uses a light colored theme (a white background).

+ * + *

XML attributes + *

+ * See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, + * {@link android.R.styleable#View View Attributes} + *

+ * + * @attr ref android.R.styleable#ProgressBar_animationResolution + * @attr ref android.R.styleable#ProgressBar_indeterminate + * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior + * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable + * @attr ref android.R.styleable#ProgressBar_indeterminateDuration + * @attr ref android.R.styleable#ProgressBar_indeterminateOnly + * @attr ref android.R.styleable#ProgressBar_interpolator + * @attr ref android.R.styleable#ProgressBar_max + * @attr ref android.R.styleable#ProgressBar_maxHeight + * @attr ref android.R.styleable#ProgressBar_maxWidth + * @attr ref android.R.styleable#ProgressBar_minHeight + * @attr ref android.R.styleable#ProgressBar_minWidth + * @attr ref android.R.styleable#ProgressBar_progress + * @attr ref android.R.styleable#ProgressBar_progressDrawable + * @attr ref android.R.styleable#ProgressBar_secondaryProgress + */ +@RemoteView +public class IcsProgressBar extends View { + private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + private static final int MAX_LEVEL = 10000; + private static final int ANIMATION_RESOLUTION = 200; + private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200; + + private static final int[] ProgressBar = new int[] { + android.R.attr.maxWidth, + android.R.attr.maxHeight, + android.R.attr.max, + android.R.attr.progress, + android.R.attr.secondaryProgress, + android.R.attr.indeterminate, + android.R.attr.indeterminateOnly, + android.R.attr.indeterminateDrawable, + android.R.attr.progressDrawable, + android.R.attr.indeterminateDuration, + android.R.attr.indeterminateBehavior, + android.R.attr.minWidth, + android.R.attr.minHeight, + android.R.attr.interpolator, + android.R.attr.animationResolution, + }; + private static final int ProgressBar_maxWidth = 0; + private static final int ProgressBar_maxHeight = 1; + private static final int ProgressBar_max = 2; + private static final int ProgressBar_progress = 3; + private static final int ProgressBar_secondaryProgress = 4; + private static final int ProgressBar_indeterminate = 5; + private static final int ProgressBar_indeterminateOnly = 6; + private static final int ProgressBar_indeterminateDrawable = 7; + private static final int ProgressBar_progressDrawable = 8; + private static final int ProgressBar_indeterminateDuration = 9; + private static final int ProgressBar_indeterminateBehavior = 10; + private static final int ProgressBar_minWidth = 11; + private static final int ProgressBar_minHeight = 12; + private static final int ProgressBar_interpolator = 13; + private static final int ProgressBar_animationResolution = 14; + + int mMinWidth; + int mMaxWidth; + int mMinHeight; + int mMaxHeight; + + private int mProgress; + private int mSecondaryProgress; + private int mMax; + + private int mBehavior; + private int mDuration; + private boolean mIndeterminate; + private boolean mOnlyIndeterminate; + private Transformation mTransformation; + private AlphaAnimation mAnimation; + private Drawable mIndeterminateDrawable; + private int mIndeterminateRealLeft; + private int mIndeterminateRealTop; + private Drawable mProgressDrawable; + private Drawable mCurrentDrawable; + Bitmap mSampleTile; + private boolean mNoInvalidate; + private Interpolator mInterpolator; + private RefreshProgressRunnable mRefreshProgressRunnable; + private long mUiThreadId; + private boolean mShouldStartAnimationDrawable; + private long mLastDrawTime; + + private boolean mInDrawing; + + private int mAnimationResolution; + + private AccessibilityManager mAccessibilityManager; + private AccessibilityEventSender mAccessibilityEventSender; + + /** + * Create a new progress bar with range 0...100 and initial progress of 0. + * @param context the application environment + */ + public IcsProgressBar(Context context) { + this(context, null); + } + + public IcsProgressBar(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.progressBarStyle); + } + + public IcsProgressBar(Context context, AttributeSet attrs, int defStyle) { + this(context, attrs, defStyle, 0); + } + + /** + * @hide + */ + public IcsProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) { + super(context, attrs, defStyle); + mUiThreadId = Thread.currentThread().getId(); + initProgressBar(); + + TypedArray a = + context.obtainStyledAttributes(attrs, /*R.styleable.*/ProgressBar, defStyle, styleRes); + + mNoInvalidate = true; + + Drawable drawable = a.getDrawable(/*R.styleable.*/ProgressBar_progressDrawable); + if (drawable != null) { + drawable = tileify(drawable, false); + // Calling this method can set mMaxHeight, make sure the corresponding + // XML attribute for mMaxHeight is read after calling this method + setProgressDrawable(drawable); + } + + + mDuration = a.getInt(/*R.styleable.*/ProgressBar_indeterminateDuration, mDuration); + + mMinWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minWidth, mMinWidth); + mMaxWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxWidth, mMaxWidth); + mMinHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minHeight, mMinHeight); + mMaxHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxHeight, mMaxHeight); + + mBehavior = a.getInt(/*R.styleable.*/ProgressBar_indeterminateBehavior, mBehavior); + + final int resID = a.getResourceId( + /*com.android.internal.R.styleable.*/ProgressBar_interpolator, + android.R.anim.linear_interpolator); // default to linear interpolator + if (resID > 0) { + setInterpolator(context, resID); + } + + setMax(a.getInt(/*R.styleable.*/ProgressBar_max, mMax)); + + setProgress(a.getInt(/*R.styleable.*/ProgressBar_progress, mProgress)); + + setSecondaryProgress( + a.getInt(/*R.styleable.*/ProgressBar_secondaryProgress, mSecondaryProgress)); + + drawable = a.getDrawable(/*R.styleable.*/ProgressBar_indeterminateDrawable); + if (drawable != null) { + drawable = tileifyIndeterminate(drawable); + setIndeterminateDrawable(drawable); + } + + mOnlyIndeterminate = a.getBoolean( + /*R.styleable.*/ProgressBar_indeterminateOnly, mOnlyIndeterminate); + + mNoInvalidate = false; + + setIndeterminate(mOnlyIndeterminate || a.getBoolean( + /*R.styleable.*/ProgressBar_indeterminate, mIndeterminate)); + + mAnimationResolution = a.getInteger(/*R.styleable.*/ProgressBar_animationResolution, + ANIMATION_RESOLUTION); + + a.recycle(); + + mAccessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE); + } + + /** + * Converts a drawable to a tiled version of itself. It will recursively + * traverse layer and state list drawables. + */ + private Drawable tileify(Drawable drawable, boolean clip) { + + if (drawable instanceof LayerDrawable) { + LayerDrawable background = (LayerDrawable) drawable; + final int N = background.getNumberOfLayers(); + Drawable[] outDrawables = new Drawable[N]; + + for (int i = 0; i < N; i++) { + int id = background.getId(i); + outDrawables[i] = tileify(background.getDrawable(i), + (id == android.R.id.progress || id == android.R.id.secondaryProgress)); + } + + LayerDrawable newBg = new LayerDrawable(outDrawables); + + for (int i = 0; i < N; i++) { + newBg.setId(i, background.getId(i)); + } + + return newBg; + + }/* else if (drawable instanceof StateListDrawable) { + StateListDrawable in = (StateListDrawable) drawable; + StateListDrawable out = new StateListDrawable(); + int numStates = in.getStateCount(); + for (int i = 0; i < numStates; i++) { + out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); + } + return out; + + }*/ else if (drawable instanceof BitmapDrawable) { + final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap(); + if (mSampleTile == null) { + mSampleTile = tileBitmap; + } + + final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); + + final BitmapShader bitmapShader = new BitmapShader(tileBitmap, + Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); + shapeDrawable.getPaint().setShader(bitmapShader); + + return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, + ClipDrawable.HORIZONTAL) : shapeDrawable; + } + + return drawable; + } + + Shape getDrawableShape() { + final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; + return new RoundRectShape(roundedCorners, null, null); + } + + /** + * Convert a AnimationDrawable for use as a barberpole animation. + * Each frame of the animation is wrapped in a ClipDrawable and + * given a tiling BitmapShader. + */ + private Drawable tileifyIndeterminate(Drawable drawable) { + if (drawable instanceof AnimationDrawable) { + AnimationDrawable background = (AnimationDrawable) drawable; + final int N = background.getNumberOfFrames(); + AnimationDrawable newBg = new AnimationDrawable(); + newBg.setOneShot(background.isOneShot()); + + for (int i = 0; i < N; i++) { + Drawable frame = tileify(background.getFrame(i), true); + frame.setLevel(10000); + newBg.addFrame(frame, background.getDuration(i)); + } + newBg.setLevel(10000); + drawable = newBg; + } + return drawable; + } + + /** + *

+ * Initialize the progress bar's default values: + *

+ *
    + *
  • progress = 0
  • + *
  • max = 100
  • + *
  • animation duration = 4000 ms
  • + *
  • indeterminate = false
  • + *
  • behavior = repeat
  • + *
+ */ + private void initProgressBar() { + mMax = 100; + mProgress = 0; + mSecondaryProgress = 0; + mIndeterminate = false; + mOnlyIndeterminate = false; + mDuration = 4000; + mBehavior = AlphaAnimation.RESTART; + mMinWidth = 24; + mMaxWidth = 48; + mMinHeight = 24; + mMaxHeight = 48; + } + + /** + *

Indicate whether this progress bar is in indeterminate mode.

+ * + * @return true if the progress bar is in indeterminate mode + */ + @ViewDebug.ExportedProperty(category = "progress") + public synchronized boolean isIndeterminate() { + return mIndeterminate; + } + + /** + *

Change the indeterminate mode for this progress bar. In indeterminate + * mode, the progress is ignored and the progress bar shows an infinite + * animation instead.

+ * + * If this progress bar's style only supports indeterminate mode (such as the circular + * progress bars), then this will be ignored. + * + * @param indeterminate true to enable the indeterminate mode + */ + public synchronized void setIndeterminate(boolean indeterminate) { + if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) { + mIndeterminate = indeterminate; + + if (indeterminate) { + // swap between indeterminate and regular backgrounds + mCurrentDrawable = mIndeterminateDrawable; + startAnimation(); + } else { + mCurrentDrawable = mProgressDrawable; + stopAnimation(); + } + } + } + + /** + *

Get the drawable used to draw the progress bar in + * indeterminate mode.

+ * + * @return a {@link android.graphics.drawable.Drawable} instance + * + * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable) + * @see #setIndeterminate(boolean) + */ + public Drawable getIndeterminateDrawable() { + return mIndeterminateDrawable; + } + + /** + *

Define the drawable used to draw the progress bar in + * indeterminate mode.

+ * + * @param d the new drawable + * + * @see #getIndeterminateDrawable() + * @see #setIndeterminate(boolean) + */ + public void setIndeterminateDrawable(Drawable d) { + if (d != null) { + d.setCallback(this); + } + mIndeterminateDrawable = d; + if (mIndeterminate) { + mCurrentDrawable = d; + postInvalidate(); + } + } + + /** + *

Get the drawable used to draw the progress bar in + * progress mode.

+ * + * @return a {@link android.graphics.drawable.Drawable} instance + * + * @see #setProgressDrawable(android.graphics.drawable.Drawable) + * @see #setIndeterminate(boolean) + */ + public Drawable getProgressDrawable() { + return mProgressDrawable; + } + + /** + *

Define the drawable used to draw the progress bar in + * progress mode.

+ * + * @param d the new drawable + * + * @see #getProgressDrawable() + * @see #setIndeterminate(boolean) + */ + public void setProgressDrawable(Drawable d) { + boolean needUpdate; + if (mProgressDrawable != null && d != mProgressDrawable) { + mProgressDrawable.setCallback(null); + needUpdate = true; + } else { + needUpdate = false; + } + + if (d != null) { + d.setCallback(this); + + // Make sure the ProgressBar is always tall enough + int drawableHeight = d.getMinimumHeight(); + if (mMaxHeight < drawableHeight) { + mMaxHeight = drawableHeight; + requestLayout(); + } + } + mProgressDrawable = d; + if (!mIndeterminate) { + mCurrentDrawable = d; + postInvalidate(); + } + + if (needUpdate) { + updateDrawableBounds(getWidth(), getHeight()); + updateDrawableState(); + doRefreshProgress(android.R.id.progress, mProgress, false, false); + doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false); + } + } + + /** + * @return The drawable currently used to draw the progress bar + */ + Drawable getCurrentDrawable() { + return mCurrentDrawable; + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return who == mProgressDrawable || who == mIndeterminateDrawable + || super.verifyDrawable(who); + } + + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); + if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); + } + + @Override + public void postInvalidate() { + if (!mNoInvalidate) { + super.postInvalidate(); + } + } + + private class RefreshProgressRunnable implements Runnable { + + private int mId; + private int mProgress; + private boolean mFromUser; + + RefreshProgressRunnable(int id, int progress, boolean fromUser) { + mId = id; + mProgress = progress; + mFromUser = fromUser; + } + + public void run() { + doRefreshProgress(mId, mProgress, mFromUser, true); + // Put ourselves back in the cache when we are done + mRefreshProgressRunnable = this; + } + + public void setup(int id, int progress, boolean fromUser) { + mId = id; + mProgress = progress; + mFromUser = fromUser; + } + + } + + private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, + boolean callBackToApp) { + float scale = mMax > 0 ? (float) progress / (float) mMax : 0; + final Drawable d = mCurrentDrawable; + if (d != null) { + Drawable progressDrawable = null; + + if (d instanceof LayerDrawable) { + progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); + } + + final int level = (int) (scale * MAX_LEVEL); + (progressDrawable != null ? progressDrawable : d).setLevel(level); + } else { + invalidate(); + } + + if (callBackToApp && id == android.R.id.progress) { + onProgressRefresh(scale, fromUser); + } + } + + void onProgressRefresh(float scale, boolean fromUser) { + if (mAccessibilityManager.isEnabled()) { + scheduleAccessibilityEventSender(); + } + } + + private synchronized void refreshProgress(int id, int progress, boolean fromUser) { + if (mUiThreadId == Thread.currentThread().getId()) { + doRefreshProgress(id, progress, fromUser, true); + } else { + RefreshProgressRunnable r; + if (mRefreshProgressRunnable != null) { + // Use cached RefreshProgressRunnable if available + r = mRefreshProgressRunnable; + // Uncache it + mRefreshProgressRunnable = null; + r.setup(id, progress, fromUser); + } else { + // Make a new one + r = new RefreshProgressRunnable(id, progress, fromUser); + } + post(r); + } + } + + /** + *

Set the current progress to the specified value. Does not do anything + * if the progress bar is in indeterminate mode.

+ * + * @param progress the new progress, between 0 and {@link #getMax()} + * + * @see #setIndeterminate(boolean) + * @see #isIndeterminate() + * @see #getProgress() + * @see #incrementProgressBy(int) + */ + public synchronized void setProgress(int progress) { + setProgress(progress, false); + } + + synchronized void setProgress(int progress, boolean fromUser) { + if (mIndeterminate) { + return; + } + + if (progress < 0) { + progress = 0; + } + + if (progress > mMax) { + progress = mMax; + } + + if (progress != mProgress) { + mProgress = progress; + refreshProgress(android.R.id.progress, mProgress, fromUser); + } + } + + /** + *

+ * Set the current secondary progress to the specified value. Does not do + * anything if the progress bar is in indeterminate mode. + *

+ * + * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} + * @see #setIndeterminate(boolean) + * @see #isIndeterminate() + * @see #getSecondaryProgress() + * @see #incrementSecondaryProgressBy(int) + */ + public synchronized void setSecondaryProgress(int secondaryProgress) { + if (mIndeterminate) { + return; + } + + if (secondaryProgress < 0) { + secondaryProgress = 0; + } + + if (secondaryProgress > mMax) { + secondaryProgress = mMax; + } + + if (secondaryProgress != mSecondaryProgress) { + mSecondaryProgress = secondaryProgress; + refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false); + } + } + + /** + *

Get the progress bar's current level of progress. Return 0 when the + * progress bar is in indeterminate mode.

+ * + * @return the current progress, between 0 and {@link #getMax()} + * + * @see #setIndeterminate(boolean) + * @see #isIndeterminate() + * @see #setProgress(int) + * @see #setMax(int) + * @see #getMax() + */ + @ViewDebug.ExportedProperty(category = "progress") + public synchronized int getProgress() { + return mIndeterminate ? 0 : mProgress; + } + + /** + *

Get the progress bar's current level of secondary progress. Return 0 when the + * progress bar is in indeterminate mode.

+ * + * @return the current secondary progress, between 0 and {@link #getMax()} + * + * @see #setIndeterminate(boolean) + * @see #isIndeterminate() + * @see #setSecondaryProgress(int) + * @see #setMax(int) + * @see #getMax() + */ + @ViewDebug.ExportedProperty(category = "progress") + public synchronized int getSecondaryProgress() { + return mIndeterminate ? 0 : mSecondaryProgress; + } + + /** + *

Return the upper limit of this progress bar's range.

+ * + * @return a positive integer + * + * @see #setMax(int) + * @see #getProgress() + * @see #getSecondaryProgress() + */ + @ViewDebug.ExportedProperty(category = "progress") + public synchronized int getMax() { + return mMax; + } + + /** + *

Set the range of the progress bar to 0...max.

+ * + * @param max the upper range of this progress bar + * + * @see #getMax() + * @see #setProgress(int) + * @see #setSecondaryProgress(int) + */ + public synchronized void setMax(int max) { + if (max < 0) { + max = 0; + } + if (max != mMax) { + mMax = max; + postInvalidate(); + + if (mProgress > max) { + mProgress = max; + } + refreshProgress(android.R.id.progress, mProgress, false); + } + } + + /** + *

Increase the progress bar's progress by the specified amount.

+ * + * @param diff the amount by which the progress must be increased + * + * @see #setProgress(int) + */ + public synchronized final void incrementProgressBy(int diff) { + setProgress(mProgress + diff); + } + + /** + *

Increase the progress bar's secondary progress by the specified amount.

+ * + * @param diff the amount by which the secondary progress must be increased + * + * @see #setSecondaryProgress(int) + */ + public synchronized final void incrementSecondaryProgressBy(int diff) { + setSecondaryProgress(mSecondaryProgress + diff); + } + + /** + *

Start the indeterminate progress animation.

+ */ + void startAnimation() { + if (getVisibility() != VISIBLE) { + return; + } + + if (mIndeterminateDrawable instanceof Animatable) { + mShouldStartAnimationDrawable = true; + mAnimation = null; + } else { + if (mInterpolator == null) { + mInterpolator = new LinearInterpolator(); + } + + mTransformation = new Transformation(); + mAnimation = new AlphaAnimation(0.0f, 1.0f); + mAnimation.setRepeatMode(mBehavior); + mAnimation.setRepeatCount(Animation.INFINITE); + mAnimation.setDuration(mDuration); + mAnimation.setInterpolator(mInterpolator); + mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); + } + postInvalidate(); + } + + /** + *

Stop the indeterminate progress animation.

+ */ + void stopAnimation() { + mAnimation = null; + mTransformation = null; + if (mIndeterminateDrawable instanceof Animatable) { + ((Animatable) mIndeterminateDrawable).stop(); + mShouldStartAnimationDrawable = false; + } + postInvalidate(); + } + + /** + * Sets the acceleration curve for the indeterminate animation. + * The interpolator is loaded as a resource from the specified context. + * + * @param context The application environment + * @param resID The resource identifier of the interpolator to load + */ + public void setInterpolator(Context context, int resID) { + setInterpolator(AnimationUtils.loadInterpolator(context, resID)); + } + + /** + * Sets the acceleration curve for the indeterminate animation. + * Defaults to a linear interpolation. + * + * @param interpolator The interpolator which defines the acceleration curve + */ + public void setInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + /** + * Gets the acceleration curve type for the indeterminate animation. + * + * @return the {@link Interpolator} associated to this animation + */ + public Interpolator getInterpolator() { + return mInterpolator; + } + + @Override + public void setVisibility(int v) { + if (getVisibility() != v) { + super.setVisibility(v); + + if (mIndeterminate) { + // let's be nice with the UI thread + if (v == GONE || v == INVISIBLE) { + stopAnimation(); + } else { + startAnimation(); + } + } + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + + if (mIndeterminate) { + // let's be nice with the UI thread + if (visibility == GONE || visibility == INVISIBLE) { + stopAnimation(); + } else { + startAnimation(); + } + } + } + + @Override + public void invalidateDrawable(Drawable dr) { + if (!mInDrawing) { + if (verifyDrawable(dr)) { + final Rect dirty = dr.getBounds(); + final int scrollX = getScrollX() + getPaddingLeft(); + final int scrollY = getScrollY() + getPaddingTop(); + + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); + } else { + super.invalidateDrawable(dr); + } + } + } + + /** + * @hide + * + @Override + public int getResolvedLayoutDirection(Drawable who) { + return (who == mProgressDrawable || who == mIndeterminateDrawable) ? + getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who); + } + */ + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + updateDrawableBounds(w, h); + } + + private void updateDrawableBounds(int w, int h) { + // onDraw will translate the canvas so we draw starting at 0,0 + int right = w - getPaddingRight() - getPaddingLeft(); + int bottom = h - getPaddingBottom() - getPaddingTop(); + int top = 0; + int left = 0; + + if (mIndeterminateDrawable != null) { + // Aspect ratio logic does not apply to AnimationDrawables + if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { + // Maintain aspect ratio. Certain kinds of animated drawables + // get very confused otherwise. + final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); + final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); + final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; + final float boundAspect = (float) w / h; + if (intrinsicAspect != boundAspect) { + if (boundAspect > intrinsicAspect) { + // New width is larger. Make it smaller to match height. + final int width = (int) (h * intrinsicAspect); + left = (w - width) / 2; + right = left + width; + } else { + // New height is larger. Make it smaller to match width. + final int height = (int) (w * (1 / intrinsicAspect)); + top = (h - height) / 2; + bottom = top + height; + } + } + } + mIndeterminateDrawable.setBounds(0, 0, right - left, bottom - top); + mIndeterminateRealLeft = left; + mIndeterminateRealTop = top; + } + + if (mProgressDrawable != null) { + mProgressDrawable.setBounds(0, 0, right, bottom); + } + } + + @Override + protected synchronized void onDraw(Canvas canvas) { + super.onDraw(canvas); + + Drawable d = mCurrentDrawable; + if (d != null) { + // Translate canvas so a indeterminate circular progress bar with padding + // rotates properly in its animation + canvas.save(); + canvas.translate(getPaddingLeft() + mIndeterminateRealLeft, getPaddingTop() + mIndeterminateRealTop); + long time = getDrawingTime(); + if (mAnimation != null) { + mAnimation.getTransformation(time, mTransformation); + float scale = mTransformation.getAlpha(); + try { + mInDrawing = true; + d.setLevel((int) (scale * MAX_LEVEL)); + } finally { + mInDrawing = false; + } + if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) { + mLastDrawTime = SystemClock.uptimeMillis(); + postInvalidateDelayed(mAnimationResolution); + } + } + d.draw(canvas); + canvas.restore(); + if (mShouldStartAnimationDrawable && d instanceof Animatable) { + ((Animatable) d).start(); + mShouldStartAnimationDrawable = false; + } + } + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Drawable d = mCurrentDrawable; + + int dw = 0; + int dh = 0; + if (d != null) { + dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); + dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); + } + updateDrawableState(); + dw += getPaddingLeft() + getPaddingRight(); + dh += getPaddingTop() + getPaddingBottom(); + + if (IS_HONEYCOMB) { + setMeasuredDimension(View.resolveSizeAndState(dw, widthMeasureSpec, 0), + View.resolveSizeAndState(dh, heightMeasureSpec, 0)); + } else { + setMeasuredDimension(View.resolveSize(dw, widthMeasureSpec), + View.resolveSize(dh, heightMeasureSpec)); + } + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + updateDrawableState(); + } + + private void updateDrawableState() { + int[] state = getDrawableState(); + + if (mProgressDrawable != null && mProgressDrawable.isStateful()) { + mProgressDrawable.setState(state); + } + + if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { + mIndeterminateDrawable.setState(state); + } + } + + static class SavedState extends BaseSavedState { + int progress; + int secondaryProgress; + + /** + * Constructor called from {@link IcsProgressBar#onSaveInstanceState()} + */ + SavedState(Parcelable superState) { + super(superState); + } + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(Parcel in) { + super(in); + progress = in.readInt(); + secondaryProgress = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(progress); + out.writeInt(secondaryProgress); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + @Override + public Parcelable onSaveInstanceState() { + // Force our ancestor class to save its state + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + + ss.progress = mProgress; + ss.secondaryProgress = mSecondaryProgress; + + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + setProgress(ss.progress); + setSecondaryProgress(ss.secondaryProgress); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mIndeterminate) { + startAnimation(); + } + } + + @Override + protected void onDetachedFromWindow() { + if (mIndeterminate) { + stopAnimation(); + } + if(mRefreshProgressRunnable != null) { + removeCallbacks(mRefreshProgressRunnable); + } + if (mAccessibilityEventSender != null) { + removeCallbacks(mAccessibilityEventSender); + } + // This should come after stopAnimation(), otherwise an invalidate message remains in the + // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation + super.onDetachedFromWindow(); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setItemCount(mMax); + event.setCurrentItemIndex(mProgress); + } + + /** + * Schedule a command for sending an accessibility event. + *
+ * Note: A command is used to ensure that accessibility events + * are sent at most one in a given time frame to save + * system resources while the progress changes quickly. + */ + private void scheduleAccessibilityEventSender() { + if (mAccessibilityEventSender == null) { + mAccessibilityEventSender = new AccessibilityEventSender(); + } else { + removeCallbacks(mAccessibilityEventSender); + } + postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); + } + + /** + * Command for sending an accessibility event. + */ + private class AccessibilityEventSender implements Runnable { + public void run() { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsSpinner.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsSpinner.java new file mode 100644 index 000000000..319c9ab83 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsSpinner.java @@ -0,0 +1,699 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.internal.widget; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import com.actionbarsherlock.R; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.PopupWindow; +import android.widget.SpinnerAdapter; + + +/** + * A view that displays one child at a time and lets the user pick among them. + * The items in the Spinner come from the {@link Adapter} associated with + * this view. + * + *

See the Spinner + * tutorial.

+ * + * @attr ref android.R.styleable#Spinner_prompt + */ +public class IcsSpinner extends IcsAbsSpinner implements OnClickListener { + //private static final String TAG = "Spinner"; + + // Only measure this many items to get a decent max width. + private static final int MAX_ITEMS_MEASURED = 15; + + /** + * Use a dialog window for selecting spinner options. + */ + //public static final int MODE_DIALOG = 0; + + /** + * Use a dropdown anchored to the Spinner for selecting spinner options. + */ + public static final int MODE_DROPDOWN = 1; + + /** + * Use the theme-supplied value to select the dropdown mode. + */ + //private static final int MODE_THEME = -1; + + private SpinnerPopup mPopup; + private DropDownAdapter mTempAdapter; + int mDropDownWidth; + + private int mGravity; + private boolean mDisableChildrenWhenDisabled; + + private Rect mTempRect = new Rect(); + + /** + * Construct a new spinner with the given context's theme, the supplied attribute set, + * and default style. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public IcsSpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SherlockSpinner, defStyle, 0); + + + DropdownPopup popup = new DropdownPopup(context, attrs, defStyle); + + mDropDownWidth = a.getLayoutDimension( + R.styleable.SherlockSpinner_android_dropDownWidth, + ViewGroup.LayoutParams.WRAP_CONTENT); + popup.setBackgroundDrawable(a.getDrawable( + R.styleable.SherlockSpinner_android_popupBackground)); + final int verticalOffset = a.getDimensionPixelOffset( + R.styleable.SherlockSpinner_android_dropDownVerticalOffset, 0); + if (verticalOffset != 0) { + popup.setVerticalOffset(verticalOffset); + } + + final int horizontalOffset = a.getDimensionPixelOffset( + R.styleable.SherlockSpinner_android_dropDownHorizontalOffset, 0); + if (horizontalOffset != 0) { + popup.setHorizontalOffset(horizontalOffset); + } + + mPopup = popup; + + mGravity = a.getInt(R.styleable.SherlockSpinner_android_gravity, Gravity.CENTER); + + mPopup.setPromptText(a.getString(R.styleable.SherlockSpinner_android_prompt)); + + mDisableChildrenWhenDisabled = true; + + a.recycle(); + + // Base constructor can call setAdapter before we initialize mPopup. + // Finish setting things up if this happened. + if (mTempAdapter != null) { + mPopup.setAdapter(mTempAdapter); + mTempAdapter = null; + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (mDisableChildrenWhenDisabled) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).setEnabled(enabled); + } + } + } + + /** + * Describes how the selected item view is positioned. Currently only the horizontal component + * is used. The default is determined by the current theme. + * + * @param gravity See {@link android.view.Gravity} + * + * @attr ref android.R.styleable#Spinner_gravity + */ + public void setGravity(int gravity) { + if (mGravity != gravity) { + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.LEFT; + } + mGravity = gravity; + requestLayout(); + } + } + + @Override + public void setAdapter(SpinnerAdapter adapter) { + super.setAdapter(adapter); + + if (mPopup != null) { + mPopup.setAdapter(new DropDownAdapter(adapter)); + } else { + mTempAdapter = new DropDownAdapter(adapter); + } + } + + @Override + public int getBaseline() { + View child = null; + + if (getChildCount() > 0) { + child = getChildAt(0); + } else if (mAdapter != null && mAdapter.getCount() > 0) { + child = makeAndAddView(0); + mRecycler.put(0, child); + removeAllViewsInLayout(); + } + + if (child != null) { + final int childBaseline = child.getBaseline(); + return childBaseline >= 0 ? child.getTop() + childBaseline : -1; + } else { + return -1; + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mPopup != null && mPopup.isShowing()) { + mPopup.dismiss(); + } + } + + /** + *

A spinner does not support item click events. Calling this method + * will raise an exception.

+ * + * @param l this listener will be ignored + */ + @Override + public void setOnItemClickListener(OnItemClickListener l) { + throw new RuntimeException("setOnItemClickListener cannot be used with a spinner."); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) { + final int measuredWidth = getMeasuredWidth(); + setMeasuredDimension(Math.min(Math.max(measuredWidth, + measureContentWidth(getAdapter(), getBackground())), + MeasureSpec.getSize(widthMeasureSpec)), + getMeasuredHeight()); + } + } + + /** + * @see android.view.View#onLayout(boolean,int,int,int,int) + * + * Creates and positions all views + * + */ + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mInLayout = true; + layout(0, false); + mInLayout = false; + } + + /** + * Creates and positions all views for this Spinner. + * + * @param delta Change in the selected position. +1 moves selection is moving to the right, + * so views are scrolling to the left. -1 means selection is moving to the left. + */ + @Override + void layout(int delta, boolean animate) { + int childrenLeft = mSpinnerPadding.left; + int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right; + + if (mDataChanged) { + handleDataChanged(); + } + + // Handle the empty set by removing all views + if (mItemCount == 0) { + resetList(); + return; + } + + if (mNextSelectedPosition >= 0) { + setSelectedPositionInt(mNextSelectedPosition); + } + + recycleAllViews(); + + // Clear out old views + removeAllViewsInLayout(); + + // Make selected view and position it + mFirstPosition = mSelectedPosition; + View sel = makeAndAddView(mSelectedPosition); + int width = sel.getMeasuredWidth(); + int selectedOffset = childrenLeft; + switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2); + break; + case Gravity.RIGHT: + selectedOffset = childrenLeft + childrenWidth - width; + break; + } + sel.offsetLeftAndRight(selectedOffset); + + // Flush any cached views that did not get reused above + mRecycler.clear(); + + invalidate(); + + checkSelectionChanged(); + + mDataChanged = false; + mNeedSync = false; + setNextSelectedPositionInt(mSelectedPosition); + } + + /** + * Obtain a view, either by pulling an existing view from the recycler or + * by getting a new one from the adapter. If we are animating, make sure + * there is enough information in the view's layout parameters to animate + * from the old to new positions. + * + * @param position Position in the spinner for the view to obtain + * @return A view that has been added to the spinner + */ + private View makeAndAddView(int position) { + + View child; + + if (!mDataChanged) { + child = mRecycler.get(position); + if (child != null) { + // Position the view + setUpChild(child); + + return child; + } + } + + // Nothing found in the recycler -- ask the adapter for a view + child = mAdapter.getView(position, null, this); + + // Position the view + setUpChild(child); + + return child; + } + + /** + * Helper for makeAndAddView to set the position of a view + * and fill out its layout paramters. + * + * @param child The view to position + */ + private void setUpChild(View child) { + + // Respect layout params that are already in the view. Otherwise + // make some up... + ViewGroup.LayoutParams lp = child.getLayoutParams(); + if (lp == null) { + lp = generateDefaultLayoutParams(); + } + + addViewInLayout(child, 0, lp); + + child.setSelected(hasFocus()); + if (mDisableChildrenWhenDisabled) { + child.setEnabled(isEnabled()); + } + + // Get measure specs + int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, + mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height); + int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, + mSpinnerPadding.left + mSpinnerPadding.right, lp.width); + + // Measure child + child.measure(childWidthSpec, childHeightSpec); + + int childLeft; + int childRight; + + // Position vertically based on gravity setting + int childTop = mSpinnerPadding.top + + ((getMeasuredHeight() - mSpinnerPadding.bottom - + mSpinnerPadding.top - child.getMeasuredHeight()) / 2); + int childBottom = childTop + child.getMeasuredHeight(); + + int width = child.getMeasuredWidth(); + childLeft = 0; + childRight = childLeft + width; + + child.layout(childLeft, childTop, childRight, childBottom); + } + + @Override + public boolean performClick() { + boolean handled = super.performClick(); + + if (!handled) { + handled = true; + + if (!mPopup.isShowing()) { + mPopup.show(); + } + } + + return handled; + } + + public void onClick(DialogInterface dialog, int which) { + setSelection(which); + dialog.dismiss(); + } + + /** + * Sets the prompt to display when the dialog is shown. + * @param prompt the prompt to set + */ + public void setPrompt(CharSequence prompt) { + mPopup.setPromptText(prompt); + } + + /** + * Sets the prompt to display when the dialog is shown. + * @param promptId the resource ID of the prompt to display when the dialog is shown + */ + public void setPromptId(int promptId) { + setPrompt(getContext().getText(promptId)); + } + + /** + * @return The prompt to display when the dialog is shown + */ + public CharSequence getPrompt() { + return mPopup.getHintText(); + } + + int measureContentWidth(SpinnerAdapter adapter, Drawable background) { + if (adapter == null) { + return 0; + } + + int width = 0; + View itemView = null; + int itemType = 0; + final int widthMeasureSpec = + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + + // Make sure the number of items we'll measure is capped. If it's a huge data set + // with wildly varying sizes, oh well. + int start = Math.max(0, getSelectedItemPosition()); + final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED); + final int count = end - start; + start = Math.max(0, start - (MAX_ITEMS_MEASURED - count)); + for (int i = start; i < end; i++) { + final int positionType = adapter.getItemViewType(i); + if (positionType != itemType) { + itemType = positionType; + itemView = null; + } + itemView = adapter.getView(i, itemView, this); + if (itemView.getLayoutParams() == null) { + itemView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + } + itemView.measure(widthMeasureSpec, heightMeasureSpec); + width = Math.max(width, itemView.getMeasuredWidth()); + } + + // Add background padding to measured width + if (background != null) { + background.getPadding(mTempRect); + width += mTempRect.left + mTempRect.right; + } + + return width; + } + + /** + *

Wrapper class for an Adapter. Transforms the embedded Adapter instance + * into a ListAdapter.

+ */ + private static class DropDownAdapter implements ListAdapter, SpinnerAdapter { + private SpinnerAdapter mAdapter; + private ListAdapter mListAdapter; + + /** + *

Creates a new ListAdapter wrapper for the specified adapter.

+ * + * @param adapter the Adapter to transform into a ListAdapter + */ + public DropDownAdapter(SpinnerAdapter adapter) { + this.mAdapter = adapter; + if (adapter instanceof ListAdapter) { + this.mListAdapter = (ListAdapter) adapter; + } + } + + public int getCount() { + return mAdapter == null ? 0 : mAdapter.getCount(); + } + + public Object getItem(int position) { + return mAdapter == null ? null : mAdapter.getItem(position); + } + + public long getItemId(int position) { + return mAdapter == null ? -1 : mAdapter.getItemId(position); + } + + public View getView(int position, View convertView, ViewGroup parent) { + return getDropDownView(position, convertView, parent); + } + + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return mAdapter == null ? null : + mAdapter.getDropDownView(position, convertView, parent); + } + + public boolean hasStableIds() { + return mAdapter != null && mAdapter.hasStableIds(); + } + + public void registerDataSetObserver(DataSetObserver observer) { + if (mAdapter != null) { + mAdapter.registerDataSetObserver(observer); + } + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(observer); + } + } + + /** + * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. + * Otherwise, return true. + */ + public boolean areAllItemsEnabled() { + final ListAdapter adapter = mListAdapter; + if (adapter != null) { + return adapter.areAllItemsEnabled(); + } else { + return true; + } + } + + /** + * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. + * Otherwise, return true. + */ + public boolean isEnabled(int position) { + final ListAdapter adapter = mListAdapter; + if (adapter != null) { + return adapter.isEnabled(position); + } else { + return true; + } + } + + public int getItemViewType(int position) { + return 0; + } + + public int getViewTypeCount() { + return 1; + } + + public boolean isEmpty() { + return getCount() == 0; + } + } + + /** + * Implements some sort of popup selection interface for selecting a spinner option. + * Allows for different spinner modes. + */ + private interface SpinnerPopup { + public void setAdapter(ListAdapter adapter); + + /** + * Show the popup + */ + public void show(); + + /** + * Dismiss the popup + */ + public void dismiss(); + + /** + * @return true if the popup is showing, false otherwise. + */ + public boolean isShowing(); + + /** + * Set hint text to be displayed to the user. This should provide + * a description of the choice being made. + * @param hintText Hint text to set. + */ + public void setPromptText(CharSequence hintText); + public CharSequence getHintText(); + } + + /* + private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener { + private AlertDialog mPopup; + private ListAdapter mListAdapter; + private CharSequence mPrompt; + + public void dismiss() { + mPopup.dismiss(); + mPopup = null; + } + + public boolean isShowing() { + return mPopup != null ? mPopup.isShowing() : false; + } + + public void setAdapter(ListAdapter adapter) { + mListAdapter = adapter; + } + + public void setPromptText(CharSequence hintText) { + mPrompt = hintText; + } + + public CharSequence getHintText() { + return mPrompt; + } + + public void show() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + if (mPrompt != null) { + builder.setTitle(mPrompt); + } + mPopup = builder.setSingleChoiceItems(mListAdapter, + getSelectedItemPosition(), this).show(); + } + + public void onClick(DialogInterface dialog, int which) { + setSelection(which); + dismiss(); + } + } + */ + + private class DropdownPopup extends IcsListPopupWindow implements SpinnerPopup { + private CharSequence mHintText; + private ListAdapter mAdapter; + + public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) { + super(context, attrs, 0, defStyleRes); + + setAnchorView(IcsSpinner.this); + setModal(true); + setPromptPosition(POSITION_PROMPT_ABOVE); + setOnItemClickListener(new OnItemClickListener() { + @SuppressWarnings("rawtypes") + public void onItemClick(AdapterView parent, View v, int position, long id) { + IcsSpinner.this.setSelection(position); + dismiss(); + } + }); + } + + @Override + public void setAdapter(ListAdapter adapter) { + super.setAdapter(adapter); + mAdapter = adapter; + } + + public CharSequence getHintText() { + return mHintText; + } + + public void setPromptText(CharSequence hintText) { + // Hint text is ignored for dropdowns, but maintain it here. + mHintText = hintText; + } + + @Override + public void show() { + final int spinnerPaddingLeft = IcsSpinner.this.getPaddingLeft(); + if (mDropDownWidth == WRAP_CONTENT) { + final int spinnerWidth = IcsSpinner.this.getWidth(); + final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight(); + setContentWidth(Math.max( + measureContentWidth((SpinnerAdapter) mAdapter, getBackground()), + spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight)); + } else if (mDropDownWidth == MATCH_PARENT) { + final int spinnerWidth = IcsSpinner.this.getWidth(); + final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight(); + setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight); + } else { + setContentWidth(mDropDownWidth); + } + final Drawable background = getBackground(); + int bgOffset = 0; + if (background != null) { + background.getPadding(mTempRect); + bgOffset = -mTempRect.left; + } + setHorizontalOffset(bgOffset + spinnerPaddingLeft); + setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + super.show(); + getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); + setSelection(IcsSpinner.this.getSelectedItemPosition()); + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsView.java new file mode 100644 index 000000000..a7185d082 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsView.java @@ -0,0 +1,21 @@ +package com.actionbarsherlock.internal.widget; + +import android.view.View; + +final class IcsView { + //No instances + private IcsView() {} + + /** + * Return only the state bits of {@link #getMeasuredWidthAndState()} + * and {@link #getMeasuredHeightAndState()}, combined into one integer. + * The width component is in the regular bits {@link #MEASURED_STATE_MASK} + * and the height component is at the shifted bits + * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}. + */ + public static int getMeasuredStateInt(View child) { + return (child.getMeasuredWidth()&View.MEASURED_STATE_MASK) + | ((child.getMeasuredHeight()>>View.MEASURED_HEIGHT_STATE_SHIFT) + & (View.MEASURED_STATE_MASK>>View.MEASURED_HEIGHT_STATE_SHIFT)); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java new file mode 100644 index 000000000..82b72d8e2 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.actionbarsherlock.internal.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.text.TextUtils.TruncateAt; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.BaseAdapter; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import com.actionbarsherlock.R; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator; +import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator; + +/** + * This widget implements the dynamic action bar tab behavior that can change + * across different configurations or circumstances. + */ +public class ScrollingTabContainerView extends HorizontalScrollView + implements IcsAdapterView.OnItemSelectedListener { + //UNUSED private static final String TAG = "ScrollingTabContainerView"; + Runnable mTabSelector; + private TabClickListener mTabClickListener; + + private IcsLinearLayout mTabLayout; + private IcsSpinner mTabSpinner; + private boolean mAllowCollapse; + + private LayoutInflater mInflater; + + int mMaxTabWidth; + private int mContentHeight; + private int mSelectedTabIndex; + + protected Animator mVisibilityAnim; + protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); + + private static final /*Time*/Interpolator sAlphaInterpolator = new DecelerateInterpolator(); + + private static final int FADE_DURATION = 200; + + public ScrollingTabContainerView(Context context) { + super(context); + setHorizontalScrollBarEnabled(false); + + TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar, + R.attr.actionBarStyle, 0); + setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0)); + a.recycle(); + + mInflater = LayoutInflater.from(context); + + mTabLayout = createTabLayout(); + addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY; + setFillViewport(lockedExpanded); + + final int childCount = mTabLayout.getChildCount(); + if (childCount > 1 && + (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) { + if (childCount > 2) { + mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f); + } else { + mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2; + } + } else { + mMaxTabWidth = -1; + } + + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY); + + final boolean canCollapse = !lockedExpanded && mAllowCollapse; + + if (canCollapse) { + // See if we should expand + mTabLayout.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec); + if (mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) { + performCollapse(); + } else { + performExpand(); + } + } else { + performExpand(); + } + + final int oldWidth = getMeasuredWidth(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + final int newWidth = getMeasuredWidth(); + + if (lockedExpanded && oldWidth != newWidth) { + // Recenter the tab display if we're at a new (scrollable) size. + setTabSelected(mSelectedTabIndex); + } + } + + /** + * Indicates whether this view is collapsed into a dropdown menu instead + * of traditional tabs. + * @return true if showing as a spinner + */ + private boolean isCollapsed() { + return mTabSpinner != null && mTabSpinner.getParent() == this; + } + + public void setAllowCollapse(boolean allowCollapse) { + mAllowCollapse = allowCollapse; + } + + private void performCollapse() { + if (isCollapsed()) return; + + if (mTabSpinner == null) { + mTabSpinner = createSpinner(); + } + removeView(mTabLayout); + addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + if (mTabSpinner.getAdapter() == null) { + mTabSpinner.setAdapter(new TabAdapter()); + } + if (mTabSelector != null) { + removeCallbacks(mTabSelector); + mTabSelector = null; + } + mTabSpinner.setSelection(mSelectedTabIndex); + } + + private boolean performExpand() { + if (!isCollapsed()) return false; + + removeView(mTabSpinner); + addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + setTabSelected(mTabSpinner.getSelectedItemPosition()); + return false; + } + + public void setTabSelected(int position) { + mSelectedTabIndex = position; + final int tabCount = mTabLayout.getChildCount(); + for (int i = 0; i < tabCount; i++) { + final View child = mTabLayout.getChildAt(i); + final boolean isSelected = i == position; + child.setSelected(isSelected); + if (isSelected) { + animateToTab(position); + } + } + } + + public void setContentHeight(int contentHeight) { + mContentHeight = contentHeight; + requestLayout(); + } + + private IcsLinearLayout createTabLayout() { + final IcsLinearLayout tabLayout = (IcsLinearLayout) LayoutInflater.from(getContext()) + .inflate(R.layout.abs__action_bar_tab_bar_view, null); + tabLayout.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT)); + return tabLayout; + } + + private IcsSpinner createSpinner() { + final IcsSpinner spinner = new IcsSpinner(getContext(), null, + R.attr.actionDropDownStyle); + spinner.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT)); + spinner.setOnItemSelectedListener(this); + return spinner; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Action bar can change size on configuration changes. + // Reread the desired height from the theme-specified style. + TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar, + R.attr.actionBarStyle, 0); + setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0)); + a.recycle(); + } + + public void animateToVisibility(int visibility) { + if (mVisibilityAnim != null) { + mVisibilityAnim.cancel(); + } + if (visibility == VISIBLE) { + if (getVisibility() != VISIBLE) { + setAlpha(0); + } + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + + anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); + anim.start(); + } else { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + + anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); + anim.start(); + } + } + + public void animateToTab(final int position) { + final View tabView = mTabLayout.getChildAt(position); + if (mTabSelector != null) { + removeCallbacks(mTabSelector); + } + mTabSelector = new Runnable() { + public void run() { + final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2; + smoothScrollTo(scrollPos, 0); + mTabSelector = null; + } + }; + post(mTabSelector); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mTabSelector != null) { + // Re-post the selector we saved + post(mTabSelector); + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mTabSelector != null) { + removeCallbacks(mTabSelector); + } + } + + private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) { + //Workaround for not being able to pass a defStyle on pre-3.0 + final TabView tabView = (TabView)mInflater.inflate(R.layout.abs__action_bar_tab, null); + tabView.init(this, tab, forAdapter); + + if (forAdapter) { + tabView.setBackgroundDrawable(null); + tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT, + mContentHeight)); + } else { + tabView.setFocusable(true); + + if (mTabClickListener == null) { + mTabClickListener = new TabClickListener(); + } + tabView.setOnClickListener(mTabClickListener); + } + return tabView; + } + + public void addTab(ActionBar.Tab tab, boolean setSelected) { + TabView tabView = createTabView(tab, false); + mTabLayout.addView(tabView, new IcsLinearLayout.LayoutParams(0, + LayoutParams.MATCH_PARENT, 1)); + if (mTabSpinner != null) { + ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); + } + if (setSelected) { + tabView.setSelected(true); + } + if (mAllowCollapse) { + requestLayout(); + } + } + + public void addTab(ActionBar.Tab tab, int position, boolean setSelected) { + final TabView tabView = createTabView(tab, false); + mTabLayout.addView(tabView, position, new IcsLinearLayout.LayoutParams( + 0, LayoutParams.MATCH_PARENT, 1)); + if (mTabSpinner != null) { + ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); + } + if (setSelected) { + tabView.setSelected(true); + } + if (mAllowCollapse) { + requestLayout(); + } + } + + public void updateTab(int position) { + ((TabView) mTabLayout.getChildAt(position)).update(); + if (mTabSpinner != null) { + ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); + } + if (mAllowCollapse) { + requestLayout(); + } + } + + public void removeTabAt(int position) { + mTabLayout.removeViewAt(position); + if (mTabSpinner != null) { + ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); + } + if (mAllowCollapse) { + requestLayout(); + } + } + + public void removeAllTabs() { + mTabLayout.removeAllViews(); + if (mTabSpinner != null) { + ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); + } + if (mAllowCollapse) { + requestLayout(); + } + } + + @Override + public void onItemSelected(IcsAdapterView parent, View view, int position, long id) { + TabView tabView = (TabView) view; + tabView.getTab().select(); + } + + @Override + public void onNothingSelected(IcsAdapterView parent) { + } + + public static class TabView extends LinearLayout { + private ScrollingTabContainerView mParent; + private ActionBar.Tab mTab; + private CapitalizingTextView mTextView; + private ImageView mIconView; + private View mCustomView; + + public TabView(Context context, AttributeSet attrs) { + //TODO super(context, null, R.attr.actionBarTabStyle); + super(context, attrs); + } + + public void init(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) { + mParent = parent; + mTab = tab; + + if (forList) { + setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + } + + update(); + } + + public void bindTab(ActionBar.Tab tab) { + mTab = tab; + update(); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Re-measure if we went beyond our maximum size. + if (mParent.mMaxTabWidth > 0 && getMeasuredWidth() > mParent.mMaxTabWidth) { + super.onMeasure(MeasureSpec.makeMeasureSpec(mParent.mMaxTabWidth, MeasureSpec.EXACTLY), + heightMeasureSpec); + } + } + + public void update() { + final ActionBar.Tab tab = mTab; + final View custom = tab.getCustomView(); + if (custom != null) { + final ViewParent customParent = custom.getParent(); + if (customParent != this) { + if (customParent != null) ((ViewGroup) customParent).removeView(custom); + addView(custom); + } + mCustomView = custom; + if (mTextView != null) mTextView.setVisibility(GONE); + if (mIconView != null) { + mIconView.setVisibility(GONE); + mIconView.setImageDrawable(null); + } + } else { + if (mCustomView != null) { + removeView(mCustomView); + mCustomView = null; + } + + final Drawable icon = tab.getIcon(); + final CharSequence text = tab.getText(); + + if (icon != null) { + if (mIconView == null) { + ImageView iconView = new ImageView(getContext()); + LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + iconView.setLayoutParams(lp); + addView(iconView, 0); + mIconView = iconView; + } + mIconView.setImageDrawable(icon); + mIconView.setVisibility(VISIBLE); + } else if (mIconView != null) { + mIconView.setVisibility(GONE); + mIconView.setImageDrawable(null); + } + + if (text != null) { + if (mTextView == null) { + CapitalizingTextView textView = new CapitalizingTextView(getContext(), null, + R.attr.actionBarTabTextStyle); + textView.setEllipsize(TruncateAt.END); + LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + textView.setLayoutParams(lp); + addView(textView); + mTextView = textView; + } + mTextView.setTextCompat(text); + mTextView.setVisibility(VISIBLE); + } else if (mTextView != null) { + mTextView.setVisibility(GONE); + mTextView.setText(null); + } + + if (mIconView != null) { + mIconView.setContentDescription(tab.getContentDescription()); + } + } + } + + public ActionBar.Tab getTab() { + return mTab; + } + } + + private class TabAdapter extends BaseAdapter { + @Override + public int getCount() { + return mTabLayout.getChildCount(); + } + + @Override + public Object getItem(int position) { + return ((TabView) mTabLayout.getChildAt(position)).getTab(); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = createTabView((ActionBar.Tab) getItem(position), true); + } else { + ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position)); + } + return convertView; + } + } + + private class TabClickListener implements OnClickListener { + public void onClick(View view) { + TabView tabView = (TabView) view; + tabView.getTab().select(); + final int tabCount = mTabLayout.getChildCount(); + for (int i = 0; i < tabCount; i++) { + final View child = mTabLayout.getChildAt(i); + child.setSelected(child == view); + } + } + } + + protected class VisibilityAnimListener implements Animator.AnimatorListener { + private boolean mCanceled = false; + private int mFinalVisibility; + + public VisibilityAnimListener withFinalVisibility(int visibility) { + mFinalVisibility = visibility; + return this; + } + + @Override + public void onAnimationStart(Animator animation) { + setVisibility(VISIBLE); + mVisibilityAnim = animation; + mCanceled = false; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) return; + + mVisibilityAnim = null; + setVisibility(mFinalVisibility); + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/ActionMode.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/ActionMode.java new file mode 100644 index 000000000..81b4cd4d2 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/ActionMode.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.view; + +import android.view.View; + + +/** + * Represents a contextual mode of the user interface. Action modes can be used for + * modal interactions with content and replace parts of the normal UI until finished. + * Examples of good action modes include selection modes, search, content editing, etc. + */ +public abstract class ActionMode { + private Object mTag; + + /** + * Set a tag object associated with this ActionMode. + * + *

Like the tag available to views, this allows applications to associate arbitrary + * data with an ActionMode for later reference. + * + * @param tag Tag to associate with this ActionMode + * + * @see #getTag() + */ + public void setTag(Object tag) { + mTag = tag; + } + + /** + * Retrieve the tag object associated with this ActionMode. + * + *

Like the tag available to views, this allows applications to associate arbitrary + * data with an ActionMode for later reference. + * + * @return Tag associated with this ActionMode + * + * @see #setTag(Object) + */ + public Object getTag() { + return mTag; + } + + /** + * Set the title of the action mode. This method will have no visible effect if + * a custom view has been set. + * + * @param title Title string to set + * + * @see #setTitle(int) + * @see #setCustomView(View) + */ + public abstract void setTitle(CharSequence title); + + /** + * Set the title of the action mode. This method will have no visible effect if + * a custom view has been set. + * + * @param resId Resource ID of a string to set as the title + * + * @see #setTitle(CharSequence) + * @see #setCustomView(View) + */ + public abstract void setTitle(int resId); + + /** + * Set the subtitle of the action mode. This method will have no visible effect if + * a custom view has been set. + * + * @param subtitle Subtitle string to set + * + * @see #setSubtitle(int) + * @see #setCustomView(View) + */ + public abstract void setSubtitle(CharSequence subtitle); + + /** + * Set the subtitle of the action mode. This method will have no visible effect if + * a custom view has been set. + * + * @param resId Resource ID of a string to set as the subtitle + * + * @see #setSubtitle(CharSequence) + * @see #setCustomView(View) + */ + public abstract void setSubtitle(int resId); + + /** + * Set a custom view for this action mode. The custom view will take the place of + * the title and subtitle. Useful for things like search boxes. + * + * @param view Custom view to use in place of the title/subtitle. + * + * @see #setTitle(CharSequence) + * @see #setSubtitle(CharSequence) + */ + public abstract void setCustomView(View view); + + /** + * Invalidate the action mode and refresh menu content. The mode's + * {@link ActionMode.Callback} will have its + * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called. + * If it returns true the menu will be scanned for updated content and any relevant changes + * will be reflected to the user. + */ + public abstract void invalidate(); + + /** + * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will + * have its {@link Callback#onDestroyActionMode(ActionMode)} method called. + */ + public abstract void finish(); + + /** + * Returns the menu of actions that this action mode presents. + * @return The action mode's menu. + */ + public abstract Menu getMenu(); + + /** + * Returns the current title of this action mode. + * @return Title text + */ + public abstract CharSequence getTitle(); + + /** + * Returns the current subtitle of this action mode. + * @return Subtitle text + */ + public abstract CharSequence getSubtitle(); + + /** + * Returns the current custom view for this action mode. + * @return The current custom view + */ + public abstract View getCustomView(); + + /** + * Returns a {@link MenuInflater} with the ActionMode's context. + */ + public abstract MenuInflater getMenuInflater(); + + /** + * Returns whether the UI presenting this action mode can take focus or not. + * This is used by internal components within the framework that would otherwise + * present an action mode UI that requires focus, such as an EditText as a custom view. + * + * @return true if the UI used to show this action mode can take focus + * @hide Internal use only + */ + public boolean isUiFocusable() { + return true; + } + + /** + * Callback interface for action modes. Supplied to + * {@link View#startActionMode(Callback)}, a Callback + * configures and handles events raised by a user's interaction with an action mode. + * + *

An action mode's lifecycle is as follows: + *

    + *
  • {@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial + * creation
  • + *
  • {@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation + * and any time the {@link ActionMode} is invalidated
  • + *
  • {@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a + * contextual action button is clicked
  • + *
  • {@link Callback#onDestroyActionMode(ActionMode)} when the action mode + * is closed
  • + *
+ */ + public interface Callback { + /** + * Called when action mode is first created. The menu supplied will be used to + * generate action buttons for the action mode. + * + * @param mode ActionMode being created + * @param menu Menu used to populate action buttons + * @return true if the action mode should be created, false if entering this + * mode should be aborted. + */ + public boolean onCreateActionMode(ActionMode mode, Menu menu); + + /** + * Called to refresh an action mode's action menu whenever it is invalidated. + * + * @param mode ActionMode being prepared + * @param menu Menu used to populate action buttons + * @return true if the menu or action mode was updated, false otherwise. + */ + public boolean onPrepareActionMode(ActionMode mode, Menu menu); + + /** + * Called to report a user click on an action button. + * + * @param mode The current ActionMode + * @param item The item that was clicked + * @return true if this callback handled the event, false if the standard MenuItem + * invocation should continue. + */ + public boolean onActionItemClicked(ActionMode mode, MenuItem item); + + /** + * Called when an action mode is about to be exited and destroyed. + * + * @param mode The current ActionMode being destroyed + */ + public void onDestroyActionMode(ActionMode mode); + } +} \ No newline at end of file diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/ActionProvider.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/ActionProvider.java new file mode 100644 index 000000000..ae7cb1fe0 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/ActionProvider.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.view; + +import android.content.Context; +import android.view.View; + +/** + * This class is a mediator for accomplishing a given task, for example sharing a file. + * It is responsible for creating a view that performs an action that accomplishes the task. + * This class also implements other functions such a performing a default action. + *

+ * An ActionProvider can be optionally specified for a {@link MenuItem} and in such a + * case it will be responsible for creating the action view that appears in the + * {@link android.app.ActionBar} as a substitute for the menu item when the item is + * displayed as an action item. Also the provider is responsible for performing a + * default action if a menu item placed on the overflow menu of the ActionBar is + * selected and none of the menu item callbacks has handled the selection. For this + * case the provider can also optionally provide a sub-menu for accomplishing the + * task at hand. + *

+ *

+ * There are two ways for using an action provider for creating and handling of action views: + *

    + *
  • + * Setting the action provider on a {@link MenuItem} directly by calling + * {@link MenuItem#setActionProvider(ActionProvider)}. + *
  • + *
  • + * Declaring the action provider in the menu XML resource. For example: + *
    + * 
    + *   <item android:id="@+id/my_menu_item"
    + *     android:title="Title"
    + *     android:icon="@drawable/my_menu_item_icon"
    + *     android:showAsAction="ifRoom"
    + *     android:actionProviderClass="foo.bar.SomeActionProvider" />
    + * 
    + * 
    + *
  • + *
+ *

+ * + * @see MenuItem#setActionProvider(ActionProvider) + * @see MenuItem#getActionProvider() + */ +public abstract class ActionProvider { + private SubUiVisibilityListener mSubUiVisibilityListener; + + /** + * Creates a new instance. + * + * @param context Context for accessing resources. + */ + public ActionProvider(Context context) { + } + + /** + * Factory method for creating new action views. + * + * @return A new action view. + */ + public abstract View onCreateActionView(); + + /** + * Performs an optional default action. + *

+ * For the case of an action provider placed in a menu item not shown as an action this + * method is invoked if previous callbacks for processing menu selection has handled + * the event. + *

+ *

+ * A menu item selection is processed in the following order: + *

    + *
  • + * Receiving a call to {@link MenuItem.OnMenuItemClickListener#onMenuItemClick + * MenuItem.OnMenuItemClickListener.onMenuItemClick}. + *
  • + *
  • + * Receiving a call to {@link android.app.Activity#onOptionsItemSelected(MenuItem) + * Activity.onOptionsItemSelected(MenuItem)} + *
  • + *
  • + * Receiving a call to {@link android.app.Fragment#onOptionsItemSelected(MenuItem) + * Fragment.onOptionsItemSelected(MenuItem)} + *
  • + *
  • + * Launching the {@link android.content.Intent} set via + * {@link MenuItem#setIntent(android.content.Intent) MenuItem.setIntent(android.content.Intent)} + *
  • + *
  • + * Invoking this method. + *
  • + *
+ *

+ *

+ * The default implementation does not perform any action and returns false. + *

+ */ + public boolean onPerformDefaultAction() { + return false; + } + + /** + * Determines if this ActionProvider has a submenu associated with it. + * + *

Associated submenus will be shown when an action view is not. This + * provider instance will receive a call to {@link #onPrepareSubMenu(SubMenu)} + * after the call to {@link #onPerformDefaultAction()} and before a submenu is + * displayed to the user. + * + * @return true if the item backed by this provider should have an associated submenu + */ + public boolean hasSubMenu() { + return false; + } + + /** + * Called to prepare an associated submenu for the menu item backed by this ActionProvider. + * + *

if {@link #hasSubMenu()} returns true, this method will be called when the + * menu item is selected to prepare the submenu for presentation to the user. Apps + * may use this to create or alter submenu content right before display. + * + * @param subMenu Submenu that will be displayed + */ + public void onPrepareSubMenu(SubMenu subMenu) { + } + + /** + * Notify the system that the visibility of an action view's sub-UI such as + * an anchored popup has changed. This will affect how other system + * visibility notifications occur. + * + * @hide Pending future API approval + */ + public void subUiVisibilityChanged(boolean isVisible) { + if (mSubUiVisibilityListener != null) { + mSubUiVisibilityListener.onSubUiVisibilityChanged(isVisible); + } + } + + /** + * @hide Internal use only + */ + public void setSubUiVisibilityListener(SubUiVisibilityListener listener) { + mSubUiVisibilityListener = listener; + } + + /** + * @hide Internal use only + */ + public interface SubUiVisibilityListener { + public void onSubUiVisibilityChanged(boolean isVisible); + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/CollapsibleActionView.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/CollapsibleActionView.java new file mode 100644 index 000000000..43281b013 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/CollapsibleActionView.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.view; + +/** + * When a {@link View} implements this interface it will receive callbacks + * when expanded or collapsed as an action view alongside the optional, + * app-specified callbacks to {@link OnActionExpandListener}. + * + *

See {@link MenuItem} for more information about action views. + * See {@link android.app.ActionBar} for more information about the action bar. + */ +public interface CollapsibleActionView { + /** + * Called when this view is expanded as an action view. + * See {@link MenuItem#expandActionView()}. + */ + public void onActionViewExpanded(); + + /** + * Called when this view is collapsed as an action view. + * See {@link MenuItem#collapseActionView()}. + */ + public void onActionViewCollapsed(); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/Menu.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/Menu.java new file mode 100644 index 000000000..951f4ccef --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/Menu.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.view; + +import android.content.ComponentName; +import android.content.Intent; +import android.view.KeyEvent; + +/** + * Interface for managing the items in a menu. + *

+ * By default, every Activity supports an options menu of actions or options. + * You can add items to this menu and handle clicks on your additions. The + * easiest way of adding menu items is inflating an XML file into the + * {@link Menu} via {@link MenuInflater}. The easiest way of attaching code to + * clicks is via {@link Activity#onOptionsItemSelected(MenuItem)} and + * {@link Activity#onContextItemSelected(MenuItem)}. + *

+ * Different menu types support different features: + *

    + *
  1. Context menus: Do not support item shortcuts and item icons. + *
  2. Options menus: The icon menus do not support item check + * marks and only show the item's + * {@link MenuItem#setTitleCondensed(CharSequence) condensed title}. The + * expanded menus (only available if six or more menu items are visible, + * reached via the 'More' item in the icon menu) do not show item icons, and + * item check marks are discouraged. + *
  3. Sub menus: Do not support item icons, or nested sub menus. + *
+ * + *
+ *

Developer Guides

+ *

For more information about creating menus, read the + * Menus developer guide.

+ *
+ */ +public interface Menu { + + /** + * This is the part of an order integer that the user can provide. + * @hide + */ + static final int USER_MASK = 0x0000ffff; + /** + * Bit shift of the user portion of the order integer. + * @hide + */ + static final int USER_SHIFT = 0; + + /** + * This is the part of an order integer that supplies the category of the + * item. + * @hide + */ + static final int CATEGORY_MASK = 0xffff0000; + /** + * Bit shift of the category portion of the order integer. + * @hide + */ + static final int CATEGORY_SHIFT = 16; + + /** + * Value to use for group and item identifier integers when you don't care + * about them. + */ + static final int NONE = 0; + + /** + * First value for group and item identifier integers. + */ + static final int FIRST = 1; + + // Implementation note: Keep these CATEGORY_* in sync with the category enum + // in attrs.xml + + /** + * Category code for the order integer for items/groups that are part of a + * container -- or/add this with your base value. + */ + static final int CATEGORY_CONTAINER = 0x00010000; + + /** + * Category code for the order integer for items/groups that are provided by + * the system -- or/add this with your base value. + */ + static final int CATEGORY_SYSTEM = 0x00020000; + + /** + * Category code for the order integer for items/groups that are + * user-supplied secondary (infrequently used) options -- or/add this with + * your base value. + */ + static final int CATEGORY_SECONDARY = 0x00030000; + + /** + * Category code for the order integer for items/groups that are + * alternative actions on the data that is currently displayed -- or/add + * this with your base value. + */ + static final int CATEGORY_ALTERNATIVE = 0x00040000; + + /** + * Flag for {@link #addIntentOptions}: if set, do not automatically remove + * any existing menu items in the same group. + */ + static final int FLAG_APPEND_TO_GROUP = 0x0001; + + /** + * Flag for {@link #performShortcut}: if set, do not close the menu after + * executing the shortcut. + */ + static final int FLAG_PERFORM_NO_CLOSE = 0x0001; + + /** + * Flag for {@link #performShortcut(int, KeyEvent, int)}: if set, always + * close the menu after executing the shortcut. Closing the menu also resets + * the prepared state. + */ + static final int FLAG_ALWAYS_PERFORM_CLOSE = 0x0002; + + /** + * Add a new item to the menu. This item displays the given title for its + * label. + * + * @param title The text to display for the item. + * @return The newly added menu item. + */ + public MenuItem add(CharSequence title); + + /** + * Add a new item to the menu. This item displays the given title for its + * label. + * + * @param titleRes Resource identifier of title string. + * @return The newly added menu item. + */ + public MenuItem add(int titleRes); + + /** + * Add a new item to the menu. This item displays the given title for its + * label. + * + * @param groupId The group identifier that this item should be part of. + * This can be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a + * group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care + * about the order. See {@link MenuItem#getOrder()}. + * @param title The text to display for the item. + * @return The newly added menu item. + */ + public MenuItem add(int groupId, int itemId, int order, CharSequence title); + + /** + * Variation on {@link #add(int, int, int, CharSequence)} that takes a + * string resource identifier instead of the string itself. + * + * @param groupId The group identifier that this item should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a + * group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care + * about the order. See {@link MenuItem#getOrder()}. + * @param titleRes Resource identifier of title string. + * @return The newly added menu item. + */ + public MenuItem add(int groupId, int itemId, int order, int titleRes); + + /** + * Add a new sub-menu to the menu. This item displays the given title for + * its label. To modify other attributes on the submenu's menu item, use + * {@link SubMenu#getItem()}. + * + * @param title The text to display for the item. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(final CharSequence title); + + /** + * Add a new sub-menu to the menu. This item displays the given title for + * its label. To modify other attributes on the submenu's menu item, use + * {@link SubMenu#getItem()}. + * + * @param titleRes Resource identifier of title string. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(final int titleRes); + + /** + * Add a new sub-menu to the menu. This item displays the given + * title for its label. To modify other attributes on the + * submenu's menu item, use {@link SubMenu#getItem()}. + *

+ * Note that you can only have one level of sub-menus, i.e. you cannnot add + * a subMenu to a subMenu: An {@link UnsupportedOperationException} will be + * thrown if you try. + * + * @param groupId The group identifier that this item should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a + * group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care + * about the order. See {@link MenuItem#getOrder()}. + * @param title The text to display for the item. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title); + + /** + * Variation on {@link #addSubMenu(int, int, int, CharSequence)} that takes + * a string resource identifier for the title instead of the string itself. + * + * @param groupId The group identifier that this item should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care about the + * order. See {@link MenuItem#getOrder()}. + * @param titleRes Resource identifier of title string. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes); + + /** + * Add a group of menu items corresponding to actions that can be performed + * for a particular Intent. The Intent is most often configured with a null + * action, the data that the current activity is working with, and includes + * either the {@link Intent#CATEGORY_ALTERNATIVE} or + * {@link Intent#CATEGORY_SELECTED_ALTERNATIVE} to find activities that have + * said they would like to be included as optional action. You can, however, + * use any Intent you want. + * + *

+ * See {@link android.content.pm.PackageManager#queryIntentActivityOptions} + * for more * details on the caller, specifics, and + * intent arguments. The list returned by that function is used + * to populate the resulting menu items. + * + *

+ * All of the menu items of possible options for the intent will be added + * with the given group and id. You can use the group to control ordering of + * the items in relation to other items in the menu. Normally this function + * will automatically remove any existing items in the menu in the same + * group and place a divider above and below the added items; this behavior + * can be modified with the flags parameter. For each of the + * generated items {@link MenuItem#setIntent} is called to associate the + * appropriate Intent with the item; this means the activity will + * automatically be started for you without having to do anything else. + * + * @param groupId The group identifier that the items should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if the items should not be in + * a group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the items. Use {@link #NONE} if you do not + * care about the order. See {@link MenuItem#getOrder()}. + * @param caller The current activity component name as defined by + * queryIntentActivityOptions(). + * @param specifics Specific items to place first as defined by + * queryIntentActivityOptions(). + * @param intent Intent describing the kinds of items to populate in the + * list as defined by queryIntentActivityOptions(). + * @param flags Additional options controlling how the items are added. + * @param outSpecificItems Optional array in which to place the menu items + * that were generated for each of the specifics that were + * requested. Entries may be null if no activity was found for that + * specific action. + * @return The number of menu items that were added. + * + * @see #FLAG_APPEND_TO_GROUP + * @see MenuItem#setIntent + * @see android.content.pm.PackageManager#queryIntentActivityOptions + */ + public int addIntentOptions(int groupId, int itemId, int order, + ComponentName caller, Intent[] specifics, + Intent intent, int flags, MenuItem[] outSpecificItems); + + /** + * Remove the item with the given identifier. + * + * @param id The item to be removed. If there is no item with this + * identifier, nothing happens. + */ + public void removeItem(int id); + + /** + * Remove all items in the given group. + * + * @param groupId The group to be removed. If there are no items in this + * group, nothing happens. + */ + public void removeGroup(int groupId); + + /** + * Remove all existing items from the menu, leaving it empty as if it had + * just been created. + */ + public void clear(); + + /** + * Control whether a particular group of items can show a check mark. This + * is similar to calling {@link MenuItem#setCheckable} on all of the menu items + * with the given group identifier, but in addition you can control whether + * this group contains a mutually-exclusive set items. This should be called + * after the items of the group have been added to the menu. + * + * @param group The group of items to operate on. + * @param checkable Set to true to allow a check mark, false to + * disallow. The default is false. + * @param exclusive If set to true, only one item in this group can be + * checked at a time; checking an item will automatically + * uncheck all others in the group. If set to false, each + * item can be checked independently of the others. + * + * @see MenuItem#setCheckable + * @see MenuItem#setChecked + */ + public void setGroupCheckable(int group, boolean checkable, boolean exclusive); + + /** + * Show or hide all menu items that are in the given group. + * + * @param group The group of items to operate on. + * @param visible If true the items are visible, else they are hidden. + * + * @see MenuItem#setVisible + */ + public void setGroupVisible(int group, boolean visible); + + /** + * Enable or disable all menu items that are in the given group. + * + * @param group The group of items to operate on. + * @param enabled If true the items will be enabled, else they will be disabled. + * + * @see MenuItem#setEnabled + */ + public void setGroupEnabled(int group, boolean enabled); + + /** + * Return whether the menu currently has item items that are visible. + * + * @return True if there is one or more item visible, + * else false. + */ + public boolean hasVisibleItems(); + + /** + * Return the menu item with a particular identifier. + * + * @param id The identifier to find. + * + * @return The menu item object, or null if there is no item with + * this identifier. + */ + public MenuItem findItem(int id); + + /** + * Get the number of items in the menu. Note that this will change any + * times items are added or removed from the menu. + * + * @return The item count. + */ + public int size(); + + /** + * Gets the menu item at the given index. + * + * @param index The index of the menu item to return. + * @return The menu item. + * @exception IndexOutOfBoundsException + * when {@code index < 0 || >= size()} + */ + public MenuItem getItem(int index); + + /** + * Closes the menu, if open. + */ + public void close(); + + /** + * Execute the menu item action associated with the given shortcut + * character. + * + * @param keyCode The keycode of the shortcut key. + * @param event Key event message. + * @param flags Additional option flags or 0. + * + * @return If the given shortcut exists and is shown, returns + * true; else returns false. + * + * @see #FLAG_PERFORM_NO_CLOSE + */ + public boolean performShortcut(int keyCode, KeyEvent event, int flags); + + /** + * Is a keypress one of the defined shortcut keys for this window. + * @param keyCode the key code from {@link KeyEvent} to check. + * @param event the {@link KeyEvent} to use to help check. + */ + boolean isShortcutKey(int keyCode, KeyEvent event); + + /** + * Execute the menu item action associated with the given menu identifier. + * + * @param id Identifier associated with the menu item. + * @param flags Additional option flags or 0. + * + * @return If the given identifier exists and is shown, returns + * true; else returns false. + * + * @see #FLAG_PERFORM_NO_CLOSE + */ + public boolean performIdentifierAction(int id, int flags); + + + /** + * Control whether the menu should be running in qwerty mode (alphabetic + * shortcuts) or 12-key mode (numeric shortcuts). + * + * @param isQwerty If true the menu will use alphabetic shortcuts; else it + * will use numeric shortcuts. + */ + public void setQwertyMode(boolean isQwerty); +} + diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/MenuInflater.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/MenuInflater.java new file mode 100644 index 000000000..969459749 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/MenuInflater.java @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * 2011 Jake Wharton + * + * 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.actionbarsherlock.view; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; +import android.view.InflateException; +import android.view.View; + +import com.actionbarsherlock.R; +import com.actionbarsherlock.internal.view.menu.MenuItemImpl; + +/** + * This class is used to instantiate menu XML files into Menu objects. + *

+ * For performance reasons, menu inflation relies heavily on pre-processing of + * XML files that is done at build time. Therefore, it is not currently possible + * to use MenuInflater with an XmlPullParser over a plain XML file at runtime; + * it only works with an XmlPullParser returned from a compiled resource (R. + * something file.) + */ +public class MenuInflater { + private static final String LOG_TAG = "MenuInflater"; + + /** Menu tag name in XML. */ + private static final String XML_MENU = "menu"; + + /** Group tag name in XML. */ + private static final String XML_GROUP = "group"; + + /** Item tag name in XML. */ + private static final String XML_ITEM = "item"; + + private static final int NO_ID = 0; + + private static final Class[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class}; + + private static final Class[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE; + + private final Object[] mActionViewConstructorArguments; + + private final Object[] mActionProviderConstructorArguments; + + private Context mContext; + + /** + * Constructs a menu inflater. + * + * @see Activity#getMenuInflater() + */ + public MenuInflater(Context context) { + mContext = context; + mActionViewConstructorArguments = new Object[] {context}; + mActionProviderConstructorArguments = mActionViewConstructorArguments; + } + + /** + * Inflate a menu hierarchy from the specified XML resource. Throws + * {@link InflateException} if there is an error. + * + * @param menuRes Resource ID for an XML layout resource to load (e.g., + * R.menu.main_activity) + * @param menu The Menu to inflate into. The items and submenus will be + * added to this Menu. + */ + public void inflate(int menuRes, Menu menu) { + XmlResourceParser parser = null; + try { + parser = mContext.getResources().getLayout(menuRes); + AttributeSet attrs = Xml.asAttributeSet(parser); + + parseMenu(parser, attrs, menu); + } catch (XmlPullParserException e) { + throw new InflateException("Error inflating menu XML", e); + } catch (IOException e) { + throw new InflateException("Error inflating menu XML", e); + } finally { + if (parser != null) parser.close(); + } + } + + /** + * Called internally to fill the given menu. If a sub menu is seen, it will + * call this recursively. + */ + private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu) + throws XmlPullParserException, IOException { + MenuState menuState = new MenuState(menu); + + int eventType = parser.getEventType(); + String tagName; + boolean lookingForEndOfUnknownTag = false; + String unknownTagName = null; + + // This loop will skip to the menu start tag + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + if (tagName.equals(XML_MENU)) { + // Go to next tag + eventType = parser.next(); + break; + } + + throw new RuntimeException("Expecting menu, got " + tagName); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + boolean reachedEndOfMenu = false; + while (!reachedEndOfMenu) { + switch (eventType) { + case XmlPullParser.START_TAG: + if (lookingForEndOfUnknownTag) { + break; + } + + tagName = parser.getName(); + if (tagName.equals(XML_GROUP)) { + menuState.readGroup(attrs); + } else if (tagName.equals(XML_ITEM)) { + menuState.readItem(attrs); + } else if (tagName.equals(XML_MENU)) { + // A menu start tag denotes a submenu for an item + SubMenu subMenu = menuState.addSubMenuItem(); + + // Parse the submenu into returned SubMenu + parseMenu(parser, attrs, subMenu); + } else { + lookingForEndOfUnknownTag = true; + unknownTagName = tagName; + } + break; + + case XmlPullParser.END_TAG: + tagName = parser.getName(); + if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) { + lookingForEndOfUnknownTag = false; + unknownTagName = null; + } else if (tagName.equals(XML_GROUP)) { + menuState.resetGroup(); + } else if (tagName.equals(XML_ITEM)) { + // Add the item if it hasn't been added (if the item was + // a submenu, it would have been added already) + if (!menuState.hasAddedItem()) { + if (menuState.itemActionProvider != null && + menuState.itemActionProvider.hasSubMenu()) { + menuState.addSubMenuItem(); + } else { + menuState.addItem(); + } + } + } else if (tagName.equals(XML_MENU)) { + reachedEndOfMenu = true; + } + break; + + case XmlPullParser.END_DOCUMENT: + throw new RuntimeException("Unexpected end of document"); + } + + eventType = parser.next(); + } + } + + private static class InflatedOnMenuItemClickListener + implements MenuItem.OnMenuItemClickListener { + private static final Class[] PARAM_TYPES = new Class[] { MenuItem.class }; + + private Context mContext; + private Method mMethod; + + public InflatedOnMenuItemClickListener(Context context, String methodName) { + mContext = context; + Class c = context.getClass(); + try { + mMethod = c.getMethod(methodName, PARAM_TYPES); + } catch (Exception e) { + InflateException ex = new InflateException( + "Couldn't resolve menu item onClick handler " + methodName + + " in class " + c.getName()); + ex.initCause(e); + throw ex; + } + } + + public boolean onMenuItemClick(MenuItem item) { + try { + if (mMethod.getReturnType() == Boolean.TYPE) { + return (Boolean) mMethod.invoke(mContext, item); + } else { + mMethod.invoke(mContext, item); + return true; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * State for the current menu. + *

+ * Groups can not be nested unless there is another menu (which will have + * its state class). + */ + private class MenuState { + private Menu menu; + + /* + * Group state is set on items as they are added, allowing an item to + * override its group state. (As opposed to set on items at the group end tag.) + */ + private int groupId; + private int groupCategory; + private int groupOrder; + private int groupCheckable; + private boolean groupVisible; + private boolean groupEnabled; + + private boolean itemAdded; + private int itemId; + private int itemCategoryOrder; + private CharSequence itemTitle; + private CharSequence itemTitleCondensed; + private int itemIconResId; + private char itemAlphabeticShortcut; + private char itemNumericShortcut; + /** + * Sync to attrs.xml enum: + * - 0: none + * - 1: all + * - 2: exclusive + */ + private int itemCheckable; + private boolean itemChecked; + private boolean itemVisible; + private boolean itemEnabled; + + /** + * Sync to attrs.xml enum, values in MenuItem: + * - 0: never + * - 1: ifRoom + * - 2: always + * - -1: Safe sentinel for "no value". + */ + private int itemShowAsAction; + + private int itemActionViewLayout; + private String itemActionViewClassName; + private String itemActionProviderClassName; + + private String itemListenerMethodName; + + private ActionProvider itemActionProvider; + + private static final int defaultGroupId = NO_ID; + private static final int defaultItemId = NO_ID; + private static final int defaultItemCategory = 0; + private static final int defaultItemOrder = 0; + private static final int defaultItemCheckable = 0; + private static final boolean defaultItemChecked = false; + private static final boolean defaultItemVisible = true; + private static final boolean defaultItemEnabled = true; + + public MenuState(final Menu menu) { + this.menu = menu; + + resetGroup(); + } + + public void resetGroup() { + groupId = defaultGroupId; + groupCategory = defaultItemCategory; + groupOrder = defaultItemOrder; + groupCheckable = defaultItemCheckable; + groupVisible = defaultItemVisible; + groupEnabled = defaultItemEnabled; + } + + /** + * Called when the parser is pointing to a group tag. + */ + public void readGroup(AttributeSet attrs) { + TypedArray a = mContext.obtainStyledAttributes(attrs, + R.styleable.SherlockMenuGroup); + + groupId = a.getResourceId(R.styleable.SherlockMenuGroup_android_id, defaultGroupId); + groupCategory = a.getInt(R.styleable.SherlockMenuGroup_android_menuCategory, defaultItemCategory); + groupOrder = a.getInt(R.styleable.SherlockMenuGroup_android_orderInCategory, defaultItemOrder); + groupCheckable = a.getInt(R.styleable.SherlockMenuGroup_android_checkableBehavior, defaultItemCheckable); + groupVisible = a.getBoolean(R.styleable.SherlockMenuGroup_android_visible, defaultItemVisible); + groupEnabled = a.getBoolean(R.styleable.SherlockMenuGroup_android_enabled, defaultItemEnabled); + + a.recycle(); + } + + /** + * Called when the parser is pointing to an item tag. + */ + public void readItem(AttributeSet attrs) { + TypedArray a = mContext.obtainStyledAttributes(attrs, + R.styleable.SherlockMenuItem); + + // Inherit attributes from the group as default value + itemId = a.getResourceId(R.styleable.SherlockMenuItem_android_id, defaultItemId); + final int category = a.getInt(R.styleable.SherlockMenuItem_android_menuCategory, groupCategory); + final int order = a.getInt(R.styleable.SherlockMenuItem_android_orderInCategory, groupOrder); + itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK); + itemTitle = a.getText(R.styleable.SherlockMenuItem_android_title); + itemTitleCondensed = a.getText(R.styleable.SherlockMenuItem_android_titleCondensed); + itemIconResId = a.getResourceId(R.styleable.SherlockMenuItem_android_icon, 0); + itemAlphabeticShortcut = + getShortcut(a.getString(R.styleable.SherlockMenuItem_android_alphabeticShortcut)); + itemNumericShortcut = + getShortcut(a.getString(R.styleable.SherlockMenuItem_android_numericShortcut)); + if (a.hasValue(R.styleable.SherlockMenuItem_android_checkable)) { + // Item has attribute checkable, use it + itemCheckable = a.getBoolean(R.styleable.SherlockMenuItem_android_checkable, false) ? 1 : 0; + } else { + // Item does not have attribute, use the group's (group can have one more state + // for checkable that represents the exclusive checkable) + itemCheckable = groupCheckable; + } + + itemChecked = a.getBoolean(R.styleable.SherlockMenuItem_android_checked, defaultItemChecked); + itemVisible = a.getBoolean(R.styleable.SherlockMenuItem_android_visible, groupVisible); + itemEnabled = a.getBoolean(R.styleable.SherlockMenuItem_android_enabled, groupEnabled); + + TypedValue value = new TypedValue(); + a.getValue(R.styleable.SherlockMenuItem_android_showAsAction, value); + itemShowAsAction = value.type == TypedValue.TYPE_INT_HEX ? value.data : -1; + + itemListenerMethodName = a.getString(R.styleable.SherlockMenuItem_android_onClick); + itemActionViewLayout = a.getResourceId(R.styleable.SherlockMenuItem_android_actionLayout, 0); + itemActionViewClassName = a.getString(R.styleable.SherlockMenuItem_android_actionViewClass); + itemActionProviderClassName = a.getString(R.styleable.SherlockMenuItem_android_actionProviderClass); + + final boolean hasActionProvider = itemActionProviderClassName != null; + if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) { + itemActionProvider = newInstance(itemActionProviderClassName, + ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE, + mActionProviderConstructorArguments); + } else { + if (hasActionProvider) { + Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'." + + " Action view already specified."); + } + itemActionProvider = null; + } + + a.recycle(); + + itemAdded = false; + } + + private char getShortcut(String shortcutString) { + if (shortcutString == null) { + return 0; + } else { + return shortcutString.charAt(0); + } + } + + private void setItem(MenuItem item) { + item.setChecked(itemChecked) + .setVisible(itemVisible) + .setEnabled(itemEnabled) + .setCheckable(itemCheckable >= 1) + .setTitleCondensed(itemTitleCondensed) + .setIcon(itemIconResId) + .setAlphabeticShortcut(itemAlphabeticShortcut) + .setNumericShortcut(itemNumericShortcut); + + if (itemShowAsAction >= 0) { + item.setShowAsAction(itemShowAsAction); + } + + if (itemListenerMethodName != null) { + if (mContext.isRestricted()) { + throw new IllegalStateException("The android:onClick attribute cannot " + + "be used within a restricted context"); + } + item.setOnMenuItemClickListener( + new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName)); + } + + if (itemCheckable >= 2) { + if (item instanceof MenuItemImpl) { + MenuItemImpl impl = (MenuItemImpl) item; + impl.setExclusiveCheckable(true); + } else { + menu.setGroupCheckable(groupId, true, true); + } + } + + boolean actionViewSpecified = false; + if (itemActionViewClassName != null) { + View actionView = (View) newInstance(itemActionViewClassName, + ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments); + item.setActionView(actionView); + actionViewSpecified = true; + } + if (itemActionViewLayout > 0) { + if (!actionViewSpecified) { + item.setActionView(itemActionViewLayout); + actionViewSpecified = true; + } else { + Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'." + + " Action view already specified."); + } + } + if (itemActionProvider != null) { + item.setActionProvider(itemActionProvider); + } + } + + public void addItem() { + itemAdded = true; + setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle)); + } + + public SubMenu addSubMenuItem() { + itemAdded = true; + SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle); + setItem(subMenu.getItem()); + return subMenu; + } + + public boolean hasAddedItem() { + return itemAdded; + } + + @SuppressWarnings("unchecked") + private T newInstance(String className, Class[] constructorSignature, + Object[] arguments) { + try { + Class clazz = mContext.getClassLoader().loadClass(className); + Constructor constructor = clazz.getConstructor(constructorSignature); + return (T) constructor.newInstance(arguments); + } catch (Exception e) { + Log.w(LOG_TAG, "Cannot instantiate class: " + className, e); + } + return null; + } + } +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/MenuItem.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/MenuItem.java new file mode 100644 index 000000000..7fc3aa430 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/MenuItem.java @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.view; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; + +/** + * Interface for direct access to a previously created menu item. + *

+ * An Item is returned by calling one of the {@link android.view.Menu#add} + * methods. + *

+ * For a feature set of specific menu types, see {@link Menu}. + * + *

+ *

Developer Guides

+ *

For information about creating menus, read the + * Menus developer guide.

+ *
+ */ +public interface MenuItem { + /* + * These should be kept in sync with attrs.xml enum constants for showAsAction + */ + /** Never show this item as a button in an Action Bar. */ + public static final int SHOW_AS_ACTION_NEVER = android.view.MenuItem.SHOW_AS_ACTION_NEVER; + /** Show this item as a button in an Action Bar if the system decides there is room for it. */ + public static final int SHOW_AS_ACTION_IF_ROOM = android.view.MenuItem.SHOW_AS_ACTION_IF_ROOM; + /** + * Always show this item as a button in an Action Bar. + * Use sparingly! If too many items are set to always show in the Action Bar it can + * crowd the Action Bar and degrade the user experience on devices with smaller screens. + * A good rule of thumb is to have no more than 2 items set to always show at a time. + */ + public static final int SHOW_AS_ACTION_ALWAYS = android.view.MenuItem.SHOW_AS_ACTION_ALWAYS; + + /** + * When this item is in the action bar, always show it with a text label even if + * it also has an icon specified. + */ + public static final int SHOW_AS_ACTION_WITH_TEXT = android.view.MenuItem.SHOW_AS_ACTION_WITH_TEXT; + + /** + * This item's action view collapses to a normal menu item. + * When expanded, the action view temporarily takes over + * a larger segment of its container. + */ + public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = android.view.MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW; + + /** + * Interface definition for a callback to be invoked when a menu item is + * clicked. + * + * @see Activity#onContextItemSelected(MenuItem) + * @see Activity#onOptionsItemSelected(MenuItem) + */ + public interface OnMenuItemClickListener { + /** + * Called when a menu item has been invoked. This is the first code + * that is executed; if it returns true, no other callbacks will be + * executed. + * + * @param item The menu item that was invoked. + * + * @return Return true to consume this click and prevent others from + * executing. + */ + public boolean onMenuItemClick(MenuItem item); + } + + /** + * Interface definition for a callback to be invoked when a menu item + * marked with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is + * expanded or collapsed. + * + * @see MenuItem#expandActionView() + * @see MenuItem#collapseActionView() + * @see MenuItem#setShowAsActionFlags(int) + */ + public interface OnActionExpandListener { + /** + * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} + * is expanded. + * @param item Item that was expanded + * @return true if the item should expand, false if expansion should be suppressed. + */ + public boolean onMenuItemActionExpand(MenuItem item); + + /** + * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} + * is collapsed. + * @param item Item that was collapsed + * @return true if the item should collapse, false if collapsing should be suppressed. + */ + public boolean onMenuItemActionCollapse(MenuItem item); + } + + /** + * Return the identifier for this menu item. The identifier can not + * be changed after the menu is created. + * + * @return The menu item's identifier. + */ + public int getItemId(); + + /** + * Return the group identifier that this menu item is part of. The group + * identifier can not be changed after the menu is created. + * + * @return The menu item's group identifier. + */ + public int getGroupId(); + + /** + * Return the category and order within the category of this item. This + * item will be shown before all items (within its category) that have + * order greater than this value. + *

+ * An order integer contains the item's category (the upper bits of the + * integer; set by or/add the category with the order within the + * category) and the ordering of the item within that category (the + * lower bits). Example categories are {@link Menu#CATEGORY_SYSTEM}, + * {@link Menu#CATEGORY_SECONDARY}, {@link Menu#CATEGORY_ALTERNATIVE}, + * {@link Menu#CATEGORY_CONTAINER}. See {@link Menu} for a full list. + * + * @return The order of this item. + */ + public int getOrder(); + + /** + * Change the title associated with this item. + * + * @param title The new text to be displayed. + * @return This Item so additional setters can be called. + */ + public MenuItem setTitle(CharSequence title); + + /** + * Change the title associated with this item. + *

+ * Some menu types do not sufficient space to show the full title, and + * instead a condensed title is preferred. See {@link Menu} for more + * information. + * + * @param title The resource id of the new text to be displayed. + * @return This Item so additional setters can be called. + * @see #setTitleCondensed(CharSequence) + */ + + public MenuItem setTitle(int title); + + /** + * Retrieve the current title of the item. + * + * @return The title. + */ + public CharSequence getTitle(); + + /** + * Change the condensed title associated with this item. The condensed + * title is used in situations where the normal title may be too long to + * be displayed. + * + * @param title The new text to be displayed as the condensed title. + * @return This Item so additional setters can be called. + */ + public MenuItem setTitleCondensed(CharSequence title); + + /** + * Retrieve the current condensed title of the item. If a condensed + * title was never set, it will return the normal title. + * + * @return The condensed title, if it exists. + * Otherwise the normal title. + */ + public CharSequence getTitleCondensed(); + + /** + * Change the icon associated with this item. This icon will not always be + * shown, so the title should be sufficient in describing this item. See + * {@link Menu} for the menu types that support icons. + * + * @param icon The new icon (as a Drawable) to be displayed. + * @return This Item so additional setters can be called. + */ + public MenuItem setIcon(Drawable icon); + + /** + * Change the icon associated with this item. This icon will not always be + * shown, so the title should be sufficient in describing this item. See + * {@link Menu} for the menu types that support icons. + *

+ * This method will set the resource ID of the icon which will be used to + * lazily get the Drawable when this item is being shown. + * + * @param iconRes The new icon (as a resource ID) to be displayed. + * @return This Item so additional setters can be called. + */ + public MenuItem setIcon(int iconRes); + + /** + * Returns the icon for this item as a Drawable (getting it from resources if it hasn't been + * loaded before). + * + * @return The icon as a Drawable. + */ + public Drawable getIcon(); + + /** + * Change the Intent associated with this item. By default there is no + * Intent associated with a menu item. If you set one, and nothing + * else handles the item, then the default behavior will be to call + * {@link android.content.Context#startActivity} with the given Intent. + * + *

Note that setIntent() can not be used with the versions of + * {@link Menu#add} that take a Runnable, because {@link Runnable#run} + * does not return a value so there is no way to tell if it handled the + * item. In this case it is assumed that the Runnable always handles + * the item, and the intent will never be started. + * + * @see #getIntent + * @param intent The Intent to associated with the item. This Intent + * object is not copied, so be careful not to + * modify it later. + * @return This Item so additional setters can be called. + */ + public MenuItem setIntent(Intent intent); + + /** + * Return the Intent associated with this item. This returns a + * reference to the Intent which you can change as desired to modify + * what the Item is holding. + * + * @see #setIntent + * @return Returns the last value supplied to {@link #setIntent}, or + * null. + */ + public Intent getIntent(); + + /** + * Change both the numeric and alphabetic shortcut associated with this + * item. Note that the shortcut will be triggered when the key that + * generates the given character is pressed alone or along with with the alt + * key. Also note that case is not significant and that alphabetic shortcut + * characters will be displayed in lower case. + *

+ * See {@link Menu} for the menu types that support shortcuts. + * + * @param numericChar The numeric shortcut key. This is the shortcut when + * using a numeric (e.g., 12-key) keyboard. + * @param alphaChar The alphabetic shortcut key. This is the shortcut when + * using a keyboard with alphabetic keys. + * @return This Item so additional setters can be called. + */ + public MenuItem setShortcut(char numericChar, char alphaChar); + + /** + * Change the numeric shortcut associated with this item. + *

+ * See {@link Menu} for the menu types that support shortcuts. + * + * @param numericChar The numeric shortcut key. This is the shortcut when + * using a 12-key (numeric) keyboard. + * @return This Item so additional setters can be called. + */ + public MenuItem setNumericShortcut(char numericChar); + + /** + * Return the char for this menu item's numeric (12-key) shortcut. + * + * @return Numeric character to use as a shortcut. + */ + public char getNumericShortcut(); + + /** + * Change the alphabetic shortcut associated with this item. The shortcut + * will be triggered when the key that generates the given character is + * pressed alone or along with with the alt key. Case is not significant and + * shortcut characters will be displayed in lower case. Note that menu items + * with the characters '\b' or '\n' as shortcuts will get triggered by the + * Delete key or Carriage Return key, respectively. + *

+ * See {@link Menu} for the menu types that support shortcuts. + * + * @param alphaChar The alphabetic shortcut key. This is the shortcut when + * using a keyboard with alphabetic keys. + * @return This Item so additional setters can be called. + */ + public MenuItem setAlphabeticShortcut(char alphaChar); + + /** + * Return the char for this menu item's alphabetic shortcut. + * + * @return Alphabetic character to use as a shortcut. + */ + public char getAlphabeticShortcut(); + + /** + * Control whether this item can display a check mark. Setting this does + * not actually display a check mark (see {@link #setChecked} for that); + * rather, it ensures there is room in the item in which to display a + * check mark. + *

+ * See {@link Menu} for the menu types that support check marks. + * + * @param checkable Set to true to allow a check mark, false to + * disallow. The default is false. + * @see #setChecked + * @see #isCheckable + * @see Menu#setGroupCheckable + * @return This Item so additional setters can be called. + */ + public MenuItem setCheckable(boolean checkable); + + /** + * Return whether the item can currently display a check mark. + * + * @return If a check mark can be displayed, returns true. + * + * @see #setCheckable + */ + public boolean isCheckable(); + + /** + * Control whether this item is shown with a check mark. Note that you + * must first have enabled checking with {@link #setCheckable} or else + * the check mark will not appear. If this item is a member of a group that contains + * mutually-exclusive items (set via {@link Menu#setGroupCheckable(int, boolean, boolean)}, + * the other items in the group will be unchecked. + *

+ * See {@link Menu} for the menu types that support check marks. + * + * @see #setCheckable + * @see #isChecked + * @see Menu#setGroupCheckable + * @param checked Set to true to display a check mark, false to hide + * it. The default value is false. + * @return This Item so additional setters can be called. + */ + public MenuItem setChecked(boolean checked); + + /** + * Return whether the item is currently displaying a check mark. + * + * @return If a check mark is displayed, returns true. + * + * @see #setChecked + */ + public boolean isChecked(); + + /** + * Sets the visibility of the menu item. Even if a menu item is not visible, + * it may still be invoked via its shortcut (to completely disable an item, + * set it to invisible and {@link #setEnabled(boolean) disabled}). + * + * @param visible If true then the item will be visible; if false it is + * hidden. + * @return This Item so additional setters can be called. + */ + public MenuItem setVisible(boolean visible); + + /** + * Return the visibility of the menu item. + * + * @return If true the item is visible; else it is hidden. + */ + public boolean isVisible(); + + /** + * Sets whether the menu item is enabled. Disabling a menu item will not + * allow it to be invoked via its shortcut. The menu item will still be + * visible. + * + * @param enabled If true then the item will be invokable; if false it is + * won't be invokable. + * @return This Item so additional setters can be called. + */ + public MenuItem setEnabled(boolean enabled); + + /** + * Return the enabled state of the menu item. + * + * @return If true the item is enabled and hence invokable; else it is not. + */ + public boolean isEnabled(); + + /** + * Check whether this item has an associated sub-menu. I.e. it is a + * sub-menu of another menu. + * + * @return If true this item has a menu; else it is a + * normal item. + */ + public boolean hasSubMenu(); + + /** + * Get the sub-menu to be invoked when this item is selected, if it has + * one. See {@link #hasSubMenu()}. + * + * @return The associated menu if there is one, else null + */ + public SubMenu getSubMenu(); + + /** + * Set a custom listener for invocation of this menu item. In most + * situations, it is more efficient and easier to use + * {@link Activity#onOptionsItemSelected(MenuItem)} or + * {@link Activity#onContextItemSelected(MenuItem)}. + * + * @param menuItemClickListener The object to receive invokations. + * @return This Item so additional setters can be called. + * @see Activity#onOptionsItemSelected(MenuItem) + * @see Activity#onContextItemSelected(MenuItem) + */ + public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener); + + /** + * Gets the extra information linked to this menu item. This extra + * information is set by the View that added this menu item to the + * menu. + * + * @see OnCreateContextMenuListener + * @return The extra information linked to the View that added this + * menu item to the menu. This can be null. + */ + public ContextMenuInfo getMenuInfo(); + + /** + * Sets how this item should display in the presence of an Action Bar. + * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS}, + * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should + * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}. + * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action, + * it should be shown with a text label. + * + * @param actionEnum How the item should display. One of + * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or + * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default. + * + * @see android.app.ActionBar + * @see #setActionView(View) + */ + public void setShowAsAction(int actionEnum); + + /** + * Sets how this item should display in the presence of an Action Bar. + * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS}, + * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should + * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}. + * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action, + * it should be shown with a text label. + * + *

Note: This method differs from {@link #setShowAsAction(int)} only in that it + * returns the current MenuItem instance for call chaining. + * + * @param actionEnum How the item should display. One of + * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or + * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default. + * + * @see android.app.ActionBar + * @see #setActionView(View) + * @return This MenuItem instance for call chaining. + */ + public MenuItem setShowAsActionFlags(int actionEnum); + + /** + * Set an action view for this menu item. An action view will be displayed in place + * of an automatically generated menu item element in the UI when this item is shown + * as an action within a parent. + *

+ * Note: Setting an action view overrides the action provider + * set via {@link #setActionProvider(ActionProvider)}. + *

+ * + * @param view View to use for presenting this item to the user. + * @return This Item so additional setters can be called. + * + * @see #setShowAsAction(int) + */ + public MenuItem setActionView(View view); + + /** + * Set an action view for this menu item. An action view will be displayed in place + * of an automatically generated menu item element in the UI when this item is shown + * as an action within a parent. + *

+ * Note: Setting an action view overrides the action provider + * set via {@link #setActionProvider(ActionProvider)}. + *

+ * + * @param resId Layout resource to use for presenting this item to the user. + * @return This Item so additional setters can be called. + * + * @see #setShowAsAction(int) + */ + public MenuItem setActionView(int resId); + + /** + * Returns the currently set action view for this menu item. + * + * @return This item's action view + * + * @see #setActionView(View) + * @see #setShowAsAction(int) + */ + public View getActionView(); + + /** + * Sets the {@link ActionProvider} responsible for creating an action view if + * the item is placed on the action bar. The provider also provides a default + * action invoked if the item is placed in the overflow menu. + *

+ * Note: Setting an action provider overrides the action view + * set via {@link #setActionView(int)} or {@link #setActionView(View)}. + *

+ * + * @param actionProvider The action provider. + * @return This Item so additional setters can be called. + * + * @see ActionProvider + */ + public MenuItem setActionProvider(ActionProvider actionProvider); + + /** + * Gets the {@link ActionProvider}. + * + * @return The action provider. + * + * @see ActionProvider + * @see #setActionProvider(ActionProvider) + */ + public ActionProvider getActionProvider(); + + /** + * Expand the action view associated with this menu item. + * The menu item must have an action view set, as well as + * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. + * If a listener has been set using {@link #setOnActionExpandListener(OnActionExpandListener)} + * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)} + * method invoked. The listener may return false from this method to prevent expanding + * the action view. + * + * @return true if the action view was expanded, false otherwise. + */ + public boolean expandActionView(); + + /** + * Collapse the action view associated with this menu item. + * The menu item must have an action view set, as well as the showAsAction flag + * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a listener has been set using + * {@link #setOnActionExpandListener(OnActionExpandListener)} it will have its + * {@link OnActionExpandListener#onMenuItemActionCollapse(MenuItem)} method invoked. + * The listener may return false from this method to prevent collapsing the action view. + * + * @return true if the action view was collapsed, false otherwise. + */ + public boolean collapseActionView(); + + /** + * Returns true if this menu item's action view has been expanded. + * + * @return true if the item's action view is expanded, false otherwise. + * + * @see #expandActionView() + * @see #collapseActionView() + * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW + * @see OnActionExpandListener + */ + public boolean isActionViewExpanded(); + + /** + * Set an {@link OnActionExpandListener} on this menu item to be notified when + * the associated action view is expanded or collapsed. The menu item must + * be configured to expand or collapse its action view using the flag + * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. + * + * @param listener Listener that will respond to expand/collapse events + * @return This menu item instance for call chaining + */ + public MenuItem setOnActionExpandListener(OnActionExpandListener listener); +} \ No newline at end of file diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/SubMenu.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/SubMenu.java new file mode 100644 index 000000000..397fd1c2d --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/SubMenu.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.actionbarsherlock.view; + +import android.graphics.drawable.Drawable; +import android.view.View; + +/** + * Subclass of {@link Menu} for sub menus. + *

+ * Sub menus do not support item icons, or nested sub menus. + * + *

+ *

Developer Guides

+ *

For information about creating menus, read the + * Menus developer guide.

+ *
+ */ + +public interface SubMenu extends Menu { + /** + * Sets the submenu header's title to the title given in titleRes + * resource identifier. + * + * @param titleRes The string resource identifier used for the title. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderTitle(int titleRes); + + /** + * Sets the submenu header's title to the title given in title. + * + * @param title The character sequence used for the title. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderTitle(CharSequence title); + + /** + * Sets the submenu header's icon to the icon given in iconRes + * resource id. + * + * @param iconRes The resource identifier used for the icon. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderIcon(int iconRes); + + /** + * Sets the submenu header's icon to the icon given in icon + * {@link Drawable}. + * + * @param icon The {@link Drawable} used for the icon. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderIcon(Drawable icon); + + /** + * Sets the header of the submenu to the {@link View} given in + * view. This replaces the header title and icon (and those + * replace this). + * + * @param view The {@link View} used for the header. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderView(View view); + + /** + * Clears the header of the submenu. + */ + public void clearHeader(); + + /** + * Change the icon associated with this submenu's item in its parent menu. + * + * @see MenuItem#setIcon(int) + * @param iconRes The new icon (as a resource ID) to be displayed. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setIcon(int iconRes); + + /** + * Change the icon associated with this submenu's item in its parent menu. + * + * @see MenuItem#setIcon(Drawable) + * @param icon The new icon (as a Drawable) to be displayed. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setIcon(Drawable icon); + + /** + * Gets the {@link MenuItem} that represents this submenu in the parent + * menu. Use this for setting additional item attributes. + * + * @return The {@link MenuItem} that launches the submenu when invoked. + */ + public MenuItem getItem(); +} diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/view/Window.java b/com_actionbarsherlock/src/com/actionbarsherlock/view/Window.java new file mode 100644 index 000000000..a340a4291 --- /dev/null +++ b/com_actionbarsherlock/src/com/actionbarsherlock/view/Window.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2011 Jake Wharton + * + * 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.actionbarsherlock.view; + +import android.content.Context; + +/** + *

Abstract base class for a top-level window look and behavior policy. An + * instance of this class should be used as the top-level view added to the + * window manager. It provides standard UI policies such as a background, title + * area, default key processing, etc.

+ * + *

The only existing implementation of this abstract class is + * android.policy.PhoneWindow, which you should instantiate when needing a + * Window. Eventually that class will be refactored and a factory method added + * for creating Window instances without knowing about a particular + * implementation.

+ */ +public abstract class Window extends android.view.Window { + public static final long FEATURE_ACTION_BAR = android.view.Window.FEATURE_ACTION_BAR; + public static final long FEATURE_ACTION_BAR_OVERLAY = android.view.Window.FEATURE_ACTION_BAR_OVERLAY; + public static final long FEATURE_ACTION_MODE_OVERLAY = android.view.Window.FEATURE_ACTION_MODE_OVERLAY; + public static final long FEATURE_NO_TITLE = android.view.Window.FEATURE_NO_TITLE; + public static final long FEATURE_PROGRESS = android.view.Window.FEATURE_PROGRESS; + public static final long FEATURE_INDETERMINATE_PROGRESS = android.view.Window.FEATURE_INDETERMINATE_PROGRESS; + + /** + * Create a new instance for a context. + * + * @param context Context. + */ + private Window(Context context) { + super(context); + } + + + public interface Callback { + /** + * Called when a panel's menu item has been selected by the user. + * + * @param featureId The panel that the menu is in. + * @param item The menu item that was selected. + * + * @return boolean Return true to finish processing of selection, or + * false to perform the normal menu handling (calling its + * Runnable or sending a Message to its target Handler). + */ + public boolean onMenuItemSelected(int featureId, MenuItem item); + } +} diff --git a/com_actionbarsherlock/test/com/actionbarsherlock/internal/ManifestParsingTest.java b/com_actionbarsherlock/test/com/actionbarsherlock/internal/ManifestParsingTest.java new file mode 100644 index 000000000..1314248a4 --- /dev/null +++ b/com_actionbarsherlock/test/com/actionbarsherlock/internal/ManifestParsingTest.java @@ -0,0 +1,39 @@ +package com.actionbarsherlock.internal; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static com.actionbarsherlock.internal.ActionBarSherlockCompat.cleanActivityName; +import com.xtremelabs.robolectric.RobolectricTestRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(RobolectricTestRunner.class) +public class ManifestParsingTest { + @Test + public void testFullyQualifiedClassName() { + String expected = "com.other.package.SomeClass"; + String actual = cleanActivityName("com.jakewharton.test", "com.other.package.SomeClass"); + assertThat(expected, equalTo(actual)); + } + + @Test + public void testFullyQualifiedClassNameSamePackage() { + String expected = "com.jakewharton.test.SomeClass"; + String actual = cleanActivityName("com.jakewharton.test", "com.jakewharton.test.SomeClass"); + assertThat(expected, equalTo(actual)); + } + + @Test + public void testUnqualifiedClassName() { + String expected = "com.jakewharton.test.SomeClass"; + String actual = cleanActivityName("com.jakewharton.test", "SomeClass"); + assertThat(expected, equalTo(actual)); + } + + @Test + public void testRelativeClassName() { + String expected = "com.jakewharton.test.ui.SomeClass"; + String actual = cleanActivityName("com.jakewharton.test", ".ui.SomeClass"); + assertThat(expected, equalTo(actual)); + } +} \ No newline at end of file diff --git a/org_apg/AndroidManifest.xml b/org_apg/AndroidManifest.xml index c98705870..4ee489bbb 100644 --- a/org_apg/AndroidManifest.xml +++ b/org_apg/AndroidManifest.xml @@ -8,7 +8,7 @@ android:versionName="1.1" > @@ -29,7 +29,8 @@ + android:label="@string/app_name" + android:theme="@style/Theme.Sherlock.Light" > - - - - - - - - - - #FFFFFF - - #333 - - - - - #ff999999 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org_apg/res/values/strings.xml b/org_apg/res/values/strings.xml index c071d041c..23574f808 100644 --- a/org_apg/res/values/strings.xml +++ b/org_apg/res/values/strings.xml @@ -331,8 +331,8 @@ Manage Keys My Keys - Encrypt File - Encrypt Text + Encrypt/Decrypt File + Encrypt/Decrypt Text Help \ No newline at end of file diff --git a/org_apg/res/values/styles.xml b/org_apg/res/values/styles.xml index b1c282cd2..7e4572c31 100644 --- a/org_apg/res/values/styles.xml +++ b/org_apg/res/values/styles.xml @@ -35,7 +35,6 @@ 2dp 14sp bold - @color/text_primary @android:color/transparent diff --git a/org_apg/src/org/apg/ui/BaseActivity.java b/org_apg/src/org/apg/ui/BaseActivity.java index 9b5039a5d..2cb04effb 100644 --- a/org_apg/src/org/apg/ui/BaseActivity.java +++ b/org_apg/src/org/apg/ui/BaseActivity.java @@ -31,6 +31,10 @@ import org.apg.ProgressDialogUpdater; import org.apg.Service; import org.apg.R; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -44,13 +48,11 @@ import android.os.Environment; import android.os.Handler; import android.os.Message; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; -public class BaseActivity extends Activity implements Runnable, ProgressDialogUpdater, +public class BaseActivity extends SherlockActivity implements Runnable, ProgressDialogUpdater, AskForSecretKeyPassPhrase.PassPhraseCallbackInterface { private ProgressDialog mProgressDialog = null; diff --git a/org_apg/src/org/apg/ui/EditKeyActivity.java b/org_apg/src/org/apg/ui/EditKeyActivity.java index c3945d4ed..8d0ed6589 100644 --- a/org_apg/src/org/apg/ui/EditKeyActivity.java +++ b/org_apg/src/org/apg/ui/EditKeyActivity.java @@ -28,6 +28,8 @@ import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.apg.R; +import com.actionbarsherlock.view.Menu; + import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; @@ -36,7 +38,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.Message; import android.view.LayoutInflater; -import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; diff --git a/org_apg/src/org/apg/ui/KeyListActivity.java b/org_apg/src/org/apg/ui/KeyListActivity.java index 6c76f02bc..74942a797 100644 --- a/org_apg/src/org/apg/ui/KeyListActivity.java +++ b/org_apg/src/org/apg/ui/KeyListActivity.java @@ -29,6 +29,8 @@ import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.apg.R; +import com.actionbarsherlock.view.MenuItem; + import android.app.AlertDialog; import android.app.Dialog; import android.app.SearchManager; @@ -42,7 +44,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -163,7 +164,7 @@ public class KeyListActivity extends BaseActivity { } @Override - public boolean onContextItemSelected(MenuItem menuItem) { + public boolean onContextItemSelected(android.view.MenuItem menuItem) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); int type = ExpandableListView.getPackedPositionType(info.packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); diff --git a/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java b/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java index 85d31779a..69f9772ef 100644 --- a/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java +++ b/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java @@ -24,19 +24,20 @@ import org.apg.ui.widget.KeyServerEditor; import org.apg.ui.widget.Editor.EditorListener; import org.apg.R; +import com.actionbarsherlock.view.Menu; + import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; -import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; -public class KeyServerPreferenceActivity extends BaseActivity - implements OnClickListener, EditorListener { +public class KeyServerPreferenceActivity extends BaseActivity implements OnClickListener, + EditorListener { private LayoutInflater mInflater; private ViewGroup mEditors; private View mAdd; @@ -63,7 +64,8 @@ public class KeyServerPreferenceActivity extends BaseActivity String servers[] = intent.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS); if (servers != null) { for (int i = 0; i < servers.length; ++i) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, mEditors, false); + KeyServerEditor view = (KeyServerEditor) mInflater.inflate( + R.layout.key_server_editor, mEditors, false); view.setEditorListener(this); view.setValue(servers[i]); mEditors.addView(view); @@ -90,7 +92,8 @@ public class KeyServerPreferenceActivity extends BaseActivity } public void onClick(View v) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, mEditors, false); + KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, + mEditors, false); view.setEditorListener(this); mEditors.addView(view); } diff --git a/org_apg/src/org/apg/ui/MainActivity.java b/org_apg/src/org/apg/ui/MainActivity.java index b50a205fc..31088cb26 100644 --- a/org_apg/src/org/apg/ui/MainActivity.java +++ b/org_apg/src/org/apg/ui/MainActivity.java @@ -25,6 +25,10 @@ import org.apg.Id; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.apg.R; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; @@ -36,8 +40,6 @@ import android.text.util.Linkify.TransformFilter; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -83,88 +85,88 @@ public class MainActivity extends BaseActivity { super.onCreate(savedInstanceState); setContentView(R.layout.main); - if (!mPreferences.hasSeenHelp()) { - showDialog(Id.dialog.help); - } - - if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) { - showDialog(Id.dialog.change_log); - } +// if (!mPreferences.hasSeenHelp()) { +// showDialog(Id.dialog.help); +// } +// +// if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) { +// showDialog(Id.dialog.change_log); +// } } @Override protected Dialog onCreateDialog(int id) { switch (id) { - case Id.dialog.change_log: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); +// case Id.dialog.change_log: { +// AlertDialog.Builder alert = new AlertDialog.Builder(this); +// +// alert.setTitle("Changes " + Apg.getFullVersion(this)); +// LayoutInflater inflater = (LayoutInflater) this +// .getSystemService(Context.LAYOUT_INFLATER_SERVICE); +// View layout = inflater.inflate(R.layout.info, null); +// TextView message = (TextView) layout.findViewById(R.id.message); +// +// message.setText("Changes:\n" + "* \n" + "\n" +// + "WARNING: be careful editing your existing keys, as they " +// + "WILL be stripped of certificates right now.\n" + "\n" +// + "Also: key cross-certification is NOT supported, so signing " +// + "with those keys will get a warning when the signature is " + "checked.\n" +// + "\n" + "I hope APG continues to be useful to you, please send " +// + "bug reports, feature wishes, feedback."); +// alert.setView(layout); +// +// alert.setCancelable(false); +// alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { +// public void onClick(DialogInterface dialog, int id) { +// MainActivity.this.removeDialog(Id.dialog.change_log); +// mPreferences.setHasSeenChangeLog(Apg.getVersion(MainActivity.this), true); +// } +// }); +// +// return alert.create(); +// } - alert.setTitle("Changes " + Apg.getFullVersion(this)); - LayoutInflater inflater = (LayoutInflater) this - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View layout = inflater.inflate(R.layout.info, null); - TextView message = (TextView) layout.findViewById(R.id.message); - - message.setText("Changes:\n" + "* \n" + "\n" - + "WARNING: be careful editing your existing keys, as they " - + "WILL be stripped of certificates right now.\n" + "\n" - + "Also: key cross-certification is NOT supported, so signing " - + "with those keys will get a warning when the signature is " + "checked.\n" - + "\n" + "I hope APG continues to be useful to you, please send " - + "bug reports, feature wishes, feedback."); - alert.setView(layout); - - alert.setCancelable(false); - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - MainActivity.this.removeDialog(Id.dialog.change_log); - mPreferences.setHasSeenChangeLog(Apg.getVersion(MainActivity.this), true); - } - }); - - return alert.create(); - } - - case Id.dialog.help: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setTitle(R.string.title_help); - - LayoutInflater inflater = (LayoutInflater) this - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View layout = inflater.inflate(R.layout.info, null); - TextView message = (TextView) layout.findViewById(R.id.message); - message.setText(R.string.text_help); - - TransformFilter packageNames = new TransformFilter() { - public final String transformUrl(final Matcher match, String url) { - String name = match.group(1).toLowerCase(); - if (name.equals("astro")) { - return "com.metago.astro"; - } else if (name.equals("k-9 mail")) { - return "com.fsck.k9"; - } else { - return "org.openintents.filemanager"; - } - } - }; - - Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)"); - String scheme = "market://search?q=pname:"; - message.setAutoLinkMask(0); - Linkify.addLinks(message, pattern, scheme, null, packageNames); - - alert.setView(layout); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - MainActivity.this.removeDialog(Id.dialog.help); - mPreferences.setHasSeenHelp(true); - } - }); - - return alert.create(); - } +// case Id.dialog.help: { +// AlertDialog.Builder alert = new AlertDialog.Builder(this); +// +// alert.setTitle(R.string.title_help); +// +// LayoutInflater inflater = (LayoutInflater) this +// .getSystemService(Context.LAYOUT_INFLATER_SERVICE); +// View layout = inflater.inflate(R.layout.info, null); +// TextView message = (TextView) layout.findViewById(R.id.message); +// message.setText(R.string.text_help); +// +// TransformFilter packageNames = new TransformFilter() { +// public final String transformUrl(final Matcher match, String url) { +// String name = match.group(1).toLowerCase(); +// if (name.equals("astro")) { +// return "com.metago.astro"; +// } else if (name.equals("k-9 mail")) { +// return "com.fsck.k9"; +// } else { +// return "org.openintents.filemanager"; +// } +// } +// }; +// +// Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)"); +// String scheme = "market://search?q=pname:"; +// message.setAutoLinkMask(0); +// Linkify.addLinks(message, pattern, scheme, null, packageNames); +// +// alert.setView(layout); +// +// alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { +// public void onClick(DialogInterface dialog, int id) { +// MainActivity.this.removeDialog(Id.dialog.help); +// mPreferences.setHasSeenHelp(true); +// } +// }); +// +// return alert.create(); +// } default: { return super.onCreateDialog(id); diff --git a/org_apg/src/org/apg/ui/PublicKeyListActivity.java b/org_apg/src/org/apg/ui/PublicKeyListActivity.java index 81a79ce33..81d2d5e73 100644 --- a/org_apg/src/org/apg/ui/PublicKeyListActivity.java +++ b/org_apg/src/org/apg/ui/PublicKeyListActivity.java @@ -27,12 +27,13 @@ import org.apg.Id.menu.option; import org.spongycastle.openpgp.PGPPublicKeyRing; import org.apg.R; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + import android.content.Intent; import android.os.Bundle; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; @@ -79,7 +80,7 @@ public class PublicKeyListActivity extends KeyListActivity { } @Override - public boolean onContextItemSelected(MenuItem menuItem) { + public boolean onContextItemSelected(android.view.MenuItem menuItem) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); int type = ExpandableListView.getPackedPositionType(info.packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); diff --git a/org_apg/src/org/apg/ui/SecretKeyListActivity.java b/org_apg/src/org/apg/ui/SecretKeyListActivity.java index a5d351bc6..be4da700a 100644 --- a/org_apg/src/org/apg/ui/SecretKeyListActivity.java +++ b/org_apg/src/org/apg/ui/SecretKeyListActivity.java @@ -28,13 +28,15 @@ import org.apg.Id.type; import org.apg.Id.menu.option; import org.apg.R; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + import android.app.Dialog; import android.content.Intent; import android.os.Bundle; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; @@ -99,7 +101,7 @@ public class SecretKeyListActivity extends KeyListActivity implements OnChildCli } @Override - public boolean onContextItemSelected(MenuItem menuItem) { + public boolean onContextItemSelected(android.view.MenuItem menuItem) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); int type = ExpandableListView.getPackedPositionType(info.packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); diff --git a/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java b/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java index 5216e7a3d..9fbf6586c 100644 --- a/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java +++ b/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java @@ -24,10 +24,12 @@ import org.apg.Id.menu; import org.apg.Id.menu.option; import org.apg.R; + +import com.actionbarsherlock.view.Menu; + import android.app.SearchManager; import android.content.Intent; import android.os.Bundle; -import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; diff --git a/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java b/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java index 191a0ecc7..1f70f4b04 100644 --- a/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java +++ b/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java @@ -22,10 +22,12 @@ import org.apg.Id.menu; import org.apg.Id.menu.option; import org.apg.R; + +import com.actionbarsherlock.view.Menu; + import android.app.SearchManager; import android.content.Intent; import android.os.Bundle; -import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView;